[Python-ideas] Yield-From: Finalization guarantees

Nick Coghlan ncoghlan at gmail.com
Thu Mar 26 03:17:24 CET 2009

Jacob Holm wrote:
> Greg Ewing wrote:
>> Jacob Holm wrote:
>>> But if you throw another exception and it is converted to a
>>> StopIteration by the subiterator, this should definitely stop the
>>> subiterator and get a return value.
>> Not if it simply raises a StopIteration from the
>> throw call. It would have to mark itself as
>> completed, return normally from the throw and
>> then raise StopIteration on the next call to
>> next() or send().
> One of us must be missing something...  If the subiterator is exhausted
> before the throw, there won't *be* a value to return from the call so
> the only options for the throw method are to raise StopIteraton, or to
> raise some other exception.

I agree with Jacob here - contextlib.contextmanager contains a similar
check in its __exit__ method. The thing to check for is the throw method
call raising StopIteration and that StopIteration instance being a
*different* exception from the one that was thrown in. (This matters
more in the contextmanager case, since it is quite legitimate for a
generator to finish and raise StopIteration from inside a with
statement, so the contextmanager needs to avoid accidentally suppressing
that exception).

Avoiding the problem of suppressing thrown in StopIteration instances
means we still need multiple inner try/except blocks rather than a large
outer one. There is also another special case to consider: since a
permitted response to "throw(GeneratorExit)" is for the iterator to just
terminate instead of reraising GeneratorExit, the thrown in exception
should be reraised unconditionally in that situation.

So the semantics would then become:

    _i = iter(EXPR)
        _u = _i.next()
    except StopIteration as _e:
        _r = _e.value
        while 1:
                _v = yield _u
                _m = getattr(_i, 'throw', None)
                if _m is not None:
                    _et, _ev, _tb = sys.exc_info()
                        _u = _m(_et, _ev, _tb)
                    except StopIteration as _e:
                        if _e is _ev or
                           _et is GeneratorExit:
                            # Don't suppress a thrown in
                            # StopIteration and handle the
                            # case where a subiterator
                            # handles GeneratorExit by
                            # terminating rather than
                            # reraising the exception
                        # The thrown in exception
                        # terminated the iterator
                        # gracefully
                        _r = _e.value
                    if _v is None:
                        _u = _i.next()
                        _u = _i.send(_v)
                except StopIteration as _e:
                    _r = _e.value
    RESULT = _r

Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia

More information about the Python-ideas mailing list