[Python-ideas] Propagating StopIteration value
Jan Kaliszewski
zuo at chopin.edu.pl
Thu Oct 11 00:51:14 CEST 2012
Hello .*
On 09.10.2012 17:28, Serhiy Storchaka wrote:
> On 09.10.12 16:07, Oscar Benjamin wrote:
>> I really should have checked this before posting but I didn't have
>> Python 3.3 available:
>
> Generator expression also eats the StopIteration value:
>
>>>> next(x for x in f())
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> StopIteration
[snip]
1.
Why shouldn't it "eat" that value? The full-generator equivalent, even
with `yield from`, will "eat" it also:
>>> def _make_this_gen_expr_equivalent():
>>> yield from f() # or: for x in f(): yield x
...
>>> g = _make_this_gen_expr_equivalent()
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
After all, any generator will "eat" the StopIteration argument unless:
* explicitly propagates it (with `return arg` or `raise
StopIteration(arg)`), or
* iterates over the subiterator "by hand" using the next() builtin or
the __next__() method and does not catch StopIteration.
2.
I believe that the new `yield from...` feature changes nothing in the
*iterator* protocol.
What it adds are only two things that can be placed in the code of a
*generator*:
1) a return statement with a value -- finishing execution of the
generator + raising StopIteration instantiated with that value passed as
the only argument,
2) a `yield from subiterator` expression which propagates the items
generated by the subiterator (not necessarily a generator) + does all
that dance with __next__/send/throw/close (see PEP 380...) + caches
StopIteration and returns the value passed with this exception (if any
value has been passed).
Not less, not more. Especially, the `yield from subiterator` expression
itself* does not propagate* its value outside the generator.
3.
The goal described by the OP could be reached with a wrapper generator
-- something like this:
def preservefrom(iter_factory, *args, which):
final_value = None
subiter = iter(which)
def catching_gen():
nonlocal final_value
try:
while True:
yield next(subiter)
except StopIteration as exc:
if exc.args:
final_value = exc.args[0]
raise
args = [arg if arg is not which else catching_gen()
for arg in args]
yield from iter_factory(*args)
return final_value
Example usage:
>>> import itertools
>>> def f():
... yield 'g'
... return 1000000
...
>>> my_gen = f()
>>> my_chain = preservefrom(itertools.chain, 'abc', 'def', my_gen,
which=my_gen)
>>> while True:
... print(next(my_chain))
...
a
b
c
d
e
f
g
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
StopIteration: 1000000
>>> my_gen = f()
>>> my_filter = preservefrom(filter, lambda x: True, my_gen,
which=my_gen)
>>> next(my_filter)
'g'
>>> next(my_filter)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: 1000000
Best regards.
*j
More information about the Python-ideas
mailing list