[Python-ideas] Generators are iterators

Terry Reedy tjreedy at udel.edu
Thu Dec 11 10:49:24 CET 2014

On 12/10/2014 4:37 PM, Guido van Rossum wrote:
> On Wed, Dec 10, 2014 at 1:34 PM, Greg Ewing <greg.ewing at canterbury.ac.nz
> <mailto:greg.ewing at canterbury.ac.nz>> wrote:
>     Chris Angelico wrote:
>         """
>         Under this proposal, generator functions and iterators would be
>         distinct, but related, concepts.  Like the mixing of text and
>         bytes in
>         Python 2, the mixing of generators and iterators has resulted in
>         certain perceived conveniences, but proper separation will make bugs
>         more visible. The distinction is simple: A generator function
>         returns
>         a generator object. The latter is an iterator, having proper
>         __iter__
>         and __next__ methods, while the former has neither and does not
>         follow
>         iterator protocol.
>         """
>     No, that's still too confused -- all of that was true before as
>     well. Generator functions have never been iterators themselves.
>     I think Nick has it nailed -- the difference is that code inside
>     a generator function implementing a __next__ method will behave
>     differently from that inside an ordinary function implementing
>     a __next__ method.
> That's hardly news though. *Except* for the possibility of raising
> StopIteration to end the iteration, the inside of a generator has always
> been completely different from the inside of a __next__ method
> implementation.

Perhaps some Python equivalent of the C code will make it clearer what 
will and (mostly) will not change.  Correct me if I am wrong, but I 
believe the function __call__ method must do something more or less like 
the following.

class function:
     def __call__(self, *arg, **kwargs):
         "Setup execution frame and either run it or pass to generator."
         frame = execution_frame(self, args, kwargs)
         # several attributes of self are used to setup the frame
         if no_yield(self.code):
             run(frame)  # until it stops
             return top(frame.stack)
             return generator(self.code, frame)

The generator must look something like the following.

class generator:
     def __iter__(self):
         return self
     def __init__(self, code, frame):
         self.gi_code = code
         self.gi_frame = frame
         self.gi_running = False
     def __next__(self):
         "Adapt gen-func yield, return to iterator return, raise S.I."
         self.gi_running = True
         if yielded():
             return top(self.gi_frame.stack)
         else:  # returned
             raise StopIteration()
             self.gi_running = False
      ... (close, send, and throw methods, not relevant here)

(I probably have some details of gi_running wrong, and ignored how 
exhausted generator calls are handled.)

Ignoring gi_code, which I believe is only there for introspection (such 
as gi_code.__name__, which I use for error-reporting), the link between 
the generator function and the generator's pre-written __next__ method 
is the suspended generator-function frame, stored as gi_frame.  This is 
what make one generator instance different from another.

Currently, a StopIteration raised during 'run(self.gi_frame)' is passed 
on like any other exception, but treated specially by the generator 
caller.  The change is to wrap the frame execution or do the equivalent 

except StopIteration()
     raise RuntimeError

All else remains the same. Do I have this right?

The rationale for the change is the bulk of the PEP.  However, the PEP 
rationale and its acceptance are based on the existing relationship 
between the execution frame of the gf body and the generator.__next__ 
method that translates the 'gf protocol' to the iterator protocol. 
Guido, Nick, and Greg have all pointed to this, but for me, thinking in 
terms of Python equivalents makes it clearer.

Terry Jan Reedy

More information about the Python-ideas mailing list