[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)
    try:
        _u = _i.next()
    except StopIteration as _e:
        _r = _e.value
    else:
        while 1:
            try:
                _v = yield _u
            except:
                _m = getattr(_i, 'throw', None)
                if _m is not None:
                    _et, _ev, _tb = sys.exc_info()
                    try:
                        _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
                            raise
                        # The thrown in exception
                        # terminated the iterator
                        # gracefully
                        _r = _e.value
                else:
                    raise
            else:
                try:
                    if _v is None:
                        _u = _i.next()
                    else:
                        _u = _i.send(_v)
                except StopIteration as _e:
                    _r = _e.value
                    break
    RESULT = _r



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



More information about the Python-ideas mailing list