[Python-ideas] PEP 479: Change StopIteration handling inside generators

Wolfgang Maier wolfgang.maier at biologie.uni-freiburg.de
Tue Nov 25 18:30:07 CET 2014


On 11/25/2014 12:03 AM, Chris Angelico wrote:
> On Tue, Nov 25, 2014 at 9:53 AM, Wolfgang Maier
> <wolfgang.maier at biologie.uni-freiburg.de> wrote:
>> In addition, the PEP leaves an iterator's __next__() method as the only
>> reasonable place where user-code should raise StopIteration.
>> So I would like to argue that instead of just turning StopIteration into
>> some other error when it's about to bubble out of a generator frame, it
>> should be converted whenever it bubbles out of *anything except an
>> iterator's __next__()*. This would include comprehensions, but also any
>> other code.
>
> There'd have to be a special case for next(), where StopIteration is
> part of the definition of the function. The question then becomes,
> what's the boundary where StopIteration is converted?
>
> The current proposal is quite simple. All the conversion happens in
> the one function that (re)starts a generator frame, gen_send_ex() in
> Objects/genobject.c. To do this for other functions, there'd need to
> be some way of saying which ones are allowed to raise StopIteration
> and which aren't.
>

Well, I'm not familiar with every implementation detail of the 
interpreter so I can't judge how difficult to implement certain things 
would be, but one solution that I could think of is:

allow StopIteration to be raised anywhere, but let it bubble up only 
*one* frame.
So if the next outer frame does not deal with it, the exception would be 
converted to UnhandledStopIteration (or something else) when it's about 
to bubble out of that outer frame.
The builtin next() would simply reset the frame count by catching and 
reraising StopIteration raised inside its argument (whether that's an 
iterator's __next__ or a generator; note that in this scenario using 
raise StopIteration instead of return inside a generator would remain 
possible).

Examples of what would happen:

using next on a generator that raises StopIteration explicitly:
=> next catches the error and reraises StopIteration

using next on a generator that returns:
=> next behaves like currently, raising StopIteration

using next on the __next__ method of an iterator:
=> next catches the error and reraises StopIteration

every direct call of an iterator's __next__ method:
=> has to be guarded by a try/except StopIteration

Likewise in the first three cases, the calling frame, which resumes when 
next returns, (and only this frame) is given a chance to handle the 
error. If that doesn't happen (i.e. the error would bubble out) it gets 
converted.

So different from the current PEP where a StopIteration must be dealt 
with explicitly using try/except only inside generators, but bubbles up 
everywhere else, here StopIteration will be special everywhere, i.e., it 
must be passed upwards explicitly through all frames or will get converted.

Back to Steven's generator expression vs comprehension example:

iterable = [iter([])]
list(next(x) for x in iterable)

would raise UnhandledStopIteration since there is no way, inside the 
generator expression to catch the StopIteration raised by next(x).

... and if that's all complete nonsense because of some technical detail 
I'm not aware of, then please excuse my ignorance.

Wolfgang



More information about the Python-ideas mailing list