[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