[Python-ideas] Yield-From: Finalization guarantees
jh at improva.dk
Wed Apr 1 15:44:48 CEST 2009
Nick Coghlan wrote:
> Jacob Holm wrote:
>> Greg Ewing wrote:
>>> Jacob Holm wrote:
>>>> will also remove some behavior that could have been useful, such as
>>>> the ability to suppress the GeneratorExit if you know what you are
>>> I'm not convinced there are any use cases for suppressing
>>> GeneratorExit in the first place. Can you provide an
>>> example that couldn't be easily done some other way?
>> I don't have any real use cases, just a few examples of things you can
>> do in #2 that become a bit uglier in #3 or #4.
> You appear to be thinking of GeneratorExit as a way to ask a generator
> to finish normally such that it still makes sense to try to return a
> value after a GeneratorExit has been thrown in to the current frame,
Yes. I am thinking that when using this for refactoring, there are
likely to be cases where the closing generator needs to provide some
final piece of information to its caller so that the caller can do *its*
finalization. Using return for that purpose has a number of control
flow advantages. If you insist we shouldn't use return for this, we
should make close raise a RuntimeError like this:
except StopIteration, e:
if e.value is not None:
raise RuntimeError('generator responded to GeneratorExit by returning with a value')
raise RuntimeError('generator ignored GeneratorExit')
Of course I would prefer to use "return e.value" instead of the first
RuntimeError, because that seems like the obvious thing to expect when
you close a generator containing "try..except GeneratorExit: return
value". And once we have close returning a value, it would be nice to
have access to that value in the context of the yield-from expression.
Attaching it to the GeneratorExit (re)raised by yield-from seems like
the only logical choice. As my third code fragment showed, you could
then explicitly recatch the GeneratorExit and get the value there.
> but that really isn't its role.
> Instead, it's more of an "Abandon Ship! Abandon Ship! All hands to the
> lifeboats!" indication that gives the generator a chance to release any
> resources it might be holding and bail out.
That might be the prevailing wisdom concerning GeneratorExit, at least
partly based on the fact that the only way to communicate anything
useful out of a closing generator is to raise another exception.
Thinking a bit about coroutines, it would be nice to use "send" for the
normal communication and "close" to shut it down and getting a final
count = 0
sum = 0
val = (yield)
sum += val
count += 1
avg = averager()
avg.next() # start coroutine
print avg.close() # prints 1.5
To do something similar today requires either a custom exception, or the
use of special values to tell the generator to yield the result. I find
this version a lot cleaner.
> The reason that close()
> accepts a StopIteration as well as a GeneratorExit is that the former
> still indicates that the generator has finalised itself, so the
> objective of calling close() has been achieved and there is no need to
> report an error.
I have argued before that accepting StopIteration in close is likely to
hide bugs in the closed generator, because the StopIteration may come
from a return in a finally clause. However, since we *are* accepting
StopIteration we might as well make it useful.
> Any code that catches GeneratorExit without reraising it is highly
> suspect, just like code that suppresses SystemExit and KeyboardInterrupt.
Explicitly catching GeneratorExit and then returning is a valid use
today that I wouldn't consider suspect. Catching GeneratorExit and then
exiting the except block by other means than a raise or return is
suspect, but has valid uses.
More information about the Python-ideas