[Python-ideas] Yield-From: Finalization guarantees
Jacob Holm
jh at improva.dk
Tue Mar 31 19:41:16 CEST 2009
Nick Coghlan wrote:
> 4, 3, 2, 1 is the position I've come around to.
>
> [...snip...]
>
> By adopting position 4, I believe the guarantees for the exception
> handling in the new expression become as simple as possible:
> - if the subiterator does not provide a throw() method, or the
> exception thrown in is GeneratorExit, then the subiterator's close()
> method (if any) is called and the thrown in exception raised in the
> current frame
> - otherwise, the exception (including traceback) is passed down to the
> subiterator's throw() method
>
Below I have attached a heavily annotated version of the expansion that
I expect for #4. This version fixes an issue I have forgotten to
mention where the subiterator is not closed due to an AttributeError
caused by a missing send method.
> With these semantics, subiterators will be finalised promptly when the
> outermost generator is finalised without any special effort on the
> developer's part and it won't be trivially easy to accidentally suppress
> GeneratorExit.
>
The way I see it, it will actually be hard to do even on purpose, unless
you are willing to take a significant performance hit by using a
non-generator wrapper for every generator.
> To my mind, the practical benefits of such an approach are enough to
> justify the deviation from the general 'inline behaviour' guideline.
>
>
I disagree, but it seems like I am the only one here that does. It
will eliminate a potential pitfall, but will also remove some behavior
that could have been useful, such as the ability to suppress the
GeneratorExit if you know what you are doing.
- Jacob
------------------------------------------------------------------------
_i = iter(EXPR) # Raises TypeError if not an iterable.
try:
_x = None # No current exception.
_y = _i.__next__() # Guaranteed to be there by iter().
while 1:
try:
_s = yield _y
except BaseException as _e:
# An exception was thrown in, either by a call to throw() on the generator or implicitly by a call
# to close().
_x = _e # Save the thrown-in exception as current.
if isinstance(_x, GeneratorExit):
_m = None # Don't forward GeneratorExit.
else:
_m = getattr(_i, 'throw', None) # Forward any other exception if there is a throw() method.
if _m is None:
# Not forwarding. Exit loop and go to finally clause (possibly via "except StopIteration"),
# which will close _i before reraising _x.
raise
_y = _m(_x)
else:
if _s is None:
# Either a send(None) or a __next__(), forward as __next__().
_x = None # No current exception
_y = _i.__next__() # Guaranteed to be there by iter().
else:
# A send(non-None). We need to handle the case where the subiterator has no send() method.
try:
_m = _i.send
except AttributeError as _e:
# No send method. Ensure that the subiterator is closed, then reraise the AttributeError.
_x = _e # Save the AttributeError as the current exception.
_m = None # Clear _m so we know _x has not been forwarded.
raise # Exit loop and go to finally clause, which will close _i before reraising _x.
else:
_x = None # No current exception.
_y = _m(s)
except StopIteration as _e:
if _e is _x:
# If _e was just thrown in, reraise it. If the exception has been forwarded to the subiterator,
# the subiterator is assumed closed. In that case _m will be non-None, so the subiterator will not be
# closed again by the finally clause. Conversely, if the exception was not forwarded _m will be None
# and the finally clause takes care of closing it before reraising the exception.
raise
# Normal return. If we get here, the StopIteration was raised by a __next__(), send() or throw() on the
# subiterator which will therefore already be closed. In this case either _x is None or _m is not None, so
# the the subiterator will not be closed again by the finally clause.
RESULT = _e.value
finally:
if _x is not None and _m is None:
# An exception is active and was not raised by the subiterator. Explicitly call close before the
# exception is automatically reraised by the finally clause. If close raises an exception, that will
# take over.
_m = getattr(_i, 'close', None)
if _m is not None:
_m()
More information about the Python-ideas
mailing list