[Python-ideas] Change how Generator Expressions handle StopIteration
Akira Li
4kir4.1i at gmail.com
Mon Nov 3 11:41:30 CET 2014
Guido van Rossum <guido at python.org> writes:
> On Sun, Nov 2, 2014 at 8:02 PM, Nick Coghlan <ncoghlan at gmail.com> wrote:
>
>> However, I also see potentially significant backwards compatibility
>> problems when it comes to helper functions that throw StopIteration to
>> terminate the calling generator - there would likely need to be some
>> kind of thread local state and a helper "iterexit()" builtin and
>> "PyIter_Exit()" C API to call instead of raising StopIteration
>> directly.
>>
>
> That's what I was afraid of. Can you point me to an example of code that
> depends on this that isn't trivial like Andrew Barnert's ensure_positive()
> example? I think that particular example, and the category it represents,
> are excessive cleverness that abuse the feature under discussion -- but you
> sound like you have helpers for context managers that couldn't be easily
> dismissed like that.
>
>
The pure Python implementation of itertools.groupby() provided in its
docs [1] uses next() as the helper function that terminates the calling
generator by raising StopIteration
[1]: https://docs.python.org/3/library/itertools.html#itertools.groupby
Here's a simplified example:
from functools import partial
def groupby(iterable):
"""
>>> ' '.join(k for k, g in groupby('AAAABBBCCDAABBB'))
'A B C D A B'
>>> ' '.join(''.join(g) for k, g in groupby('AAAABBBCCDAABBB'))
'AAAA BBB CC D AA BBB'
"""
next_value = partial(next, iter(iterable))
def yield_same(group_value_): # generate group values
nonlocal value
while value == group_value_:
yield value
value = next_value() # exit on StopIteration
group_value = value = object()
while True:
while value == group_value: # discard unconsumed values
value = next_value() # exit on StopIteration
group_value = value
yield group_value, yield_same(group_value)
The alternative is to return a sentinel from next():
def groupby_done(iterable):
done = object() # sentinel
next_value = partial(next, iter(iterable), done)
def yield_same(group_value_): # generate group values
nonlocal value
while value == group_value_:
yield value
value = next_value()
if value is done:
return
group_value = value = object()
while value is not done:
while value == group_value: # discard unconsumed values
value = next_value()
if value is done:
return
group_value = value
yield group_value, yield_same(group_value)
The first code example exploits the fact that `next(it)` continues to
raise StopIteration on subsequent calls. The second code example has to
propagate up the stack the termination condition manually (`while True`
is replace with `while value is not done`).
--
Akira
More information about the Python-ideas
mailing list