question about generators

Carel Fellinger carel.fellinger at iae.nl
Fri Aug 16 03:08:36 CEST 2002


On Thu, Aug 15, 2002 at 04:02:03PM +0000, Andrew Koenig wrote:
> Steve> But don't you think this is because of the fundamentally
> Steve> different nature of "print" and "yield"?
> 
> At one level, of course I do -- but on another level, I don't.
> 
> A generator is a program that yields a sequence of values
> to its caller.  If I have a program that prints a sequence
> of values -- that is, a program that yields a sequence of
> values to the printer -- one might think that by changing all
> of the print statements to yield statements (ignoring formatting
> issues, of course), one could change one program to another.

And here lies the root of your confusion:), the sequence sent to
the printer is more like a single string, sent in bits and pieces.
And though those bits and pieces themselves are strings, what the
printer sees is one single stream of characters, i.e. a single string.

To me the print statement is more like an elaborate *join* function,
joining strings with spaces and the occasional newline character(s).

Moreover it does this secretely, as a side effect.  no matter where the
print statement is put in your source, all of its output will magically
appear on the same paper.

Generators, on the other hand, really are on pair with plain sequences
without trickery or wichcraft.  If you want to join such a sequence
you'll have to do it yourself, and if you want to extent one sequence
with another you'll really have to code it.


Let's remove some of the wichcraft and use StringIO.  And, as a bonus,
find the proper way to refactor print statements to keep its output
within program control.

>>> import StringIO
>>> printer = StringIO.StringIO()
>>> def f():
...     print >> printer, "spam spam", "and spam"
...     print >> printer, "or even more spam",
...
>>> f()
>>> printer.getvalue()
'spam spam and spam\nor even more spam'
>>> list(printer.getvalue())
['s', 'p', 'a', 'm', ' ', 'a', 'n', 'd', ' ', 's', 'p', 'a', 'm', '\n', 'e', 'v', 'e', 'n', ' ', 'm', 'o', 'r', 'e', ' ', 's', 'p', 'a', 'm']

>>> def g():
...     yield "spam spam", "and spam"   #this yields a tuple!
...     yield "or even more spam"
...
>>> list(r)
[('spam', 'and spam'), 'or even more spam']


This simple example show that even without recursion print doesn't behave
like yield at all.  That's why Aahz and Steve urged you to rewrite it
with return statements.  But I would rather forget about generators and
try to refactor with StringIO instead.

 
> In other words, let's go back to my original example:
> 

Let's make the printer visible:

          import StringIO
          printer = StringIO.StringIO()

>         def f():

          def f(printer):   #and keep it visible

>            for <...>:
>               if <condition>:
>                  print <something>

                   print >> printer, <something>   #keep it visible

>               else:
>                  <do something>
>                  f()

                   f(printer)    #just keep the printer visible

>                  <do something else>

The rest of the program need not change, just refer to printer.getvalue()
to see what otherwise would have appeared on paper.


-- 
groetjes, carel





More information about the Python-list mailing list