question about generators

Bengt Richter bokr at oz.net
Fri Aug 16 16:37:06 EDT 2002


On Fri, 16 Aug 2002 07:35:32 -0700, Neil Schemenauer <nas at python.ca> wrote:

>David Eppstein wrote:
>> Tim has proposed a "yield every x()" syntactic-sugar that would allow 
>> you to take a generator factory x and use it as if it were a 
>> subgenerator.  This seems a reasonable idea, but there is an efficiency 
>> argument for having a direct syntax for subgenerators:
>
>I think that depends on how 'yield every' works.  Does it require a
>generator-iterator or just any iterator?  Also, does it allow the
>generator-iterator to be passed?  For example,
>
>    def grange(n):
>        for i in xrange(n):
>           yield i
>
>    def grange_wrapper():
                       ^^(n)                     
>        return grange()
                      ^^(n)                     
>
>    def a():
>        yield every grange(10)
>
>    def b():
>        yield every grange_wrapper(10)
>
>    def c():
>        yield every range(10)
>        
>do 'a', 'b', 'c' all work?
>

I think if you substitute fakegen below for grange,
the answers should be the same. The object returned just
needs to be iterable, and yield every is sugar for an implicit
for loop using the .next method of the object returned by
the expression in "yield every <expression>". It doesn't
matter how you write the expression, so long as it evaluates
to an iterable object in some viable state (not necessarily
its first, but usually so).

Which BTW means you should be able to write
    f = fakegen(25)
    f.next()
    yield every f
to throw the first away and get all the rest

 >>> class fakegen:
 ...     def __iter__(self): return self
 ...     def __init__(self, n):
 ...         self.n = n
 ...         self.i = 0
 ...     def next(self):
 ...         i = self.i
 ...         if i < self.n:
 ...             self.i += 1
 ...             return i  # yield i
 ...         else:
 ...             raise StopIteration
 ...
 >>> for x in fakegen(5): print x,
 ...
 0 1 2 3 4

A lot of folks seem to be forgetting that an initialized _instance_ of something
has to be created first, and then used via .next() to step to the next yield result.

I.e., the "for x in fakegen(n)" obscures that this is happening:
 >>> f=fakegen(3)
 >>> f.next()
 0
 >>> f.next()
 1
 >>> f.next()
 2
 >>> f.next()
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 12, in next
 StopIteration

Likewise "yield every fakegen(n)" obscures the fact that an instance of
something must be created and stepped through via .next().

Note that you could bind to the next method of a new generator:
 >>> g=fakegen(3).next
 >>> g()
 0
 >>> g()
 1
 >>> g()
 2
 >>> g()
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 12, in next
 StopIteration

So presumably if g were visible you could write yield g()
in several different places inside another generator, and expect to
get the results in the order called. (Which is quite different from 'every').

Anyway, if I understand correctly,  yields in the body of a def fun(...): ... cause
'fun' effectively to be bound to a factory function (apparently based on a flag
associated with the code so as to divert normal execution to do the factory job),
not the function it looks like in dis.dis(fun).

The factory function acts like the fakegen class above and returns an initialized
iterable object. That object makes use of what you see in dis.dis(fun), and maintains
state (frame etc) to walk from yield to yield.

At least, this concept of things seems to work in making sense of what I've seen so far ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list