[Python-ideas] Yield-From: Handling of GeneratorExit

Jacob Holm jh at improva.dk
Thu Apr 2 16:23:03 CEST 2009

Nick Coghlan wrote:
> Greg Ewing wrote:
>> I've had another idea about this. Suppose the close()
>> method of a generator didn't complain about reaching
>> a yield after GeneratorExit is raised, but simply
>> raised it again, and continued doing so until either
>> a return occured or an exception propagated out.
>> Seems to me this couldn't do any harm to a well-
>> behaved generator, since it has to be prepared to
>> deal with a GeneratorExit arising from any of its
>> yield points.

It solves the returnvalue thing, but introduces a change for existing 
generators. Well-behaved generators would not be affected, but there 
might be generators in real use that relied on the ability to ignore 
close or code using such generators that relied on getting the RuntimeError.

If there is a use case for ignoring close, that would be better served 
by another new idea I just had, the "yield raise" expression. The 
purpose of this would be to raise an exception in the caller of "next", 
"send", "throw" or "close" *without* finalizing the generator. Extending 
my "averager" example a bit:

def averager(start=0):
   count = 0
   exc = None
   sum = start
   while 1:
           val = (yield) if exc is None else (yield raise exc)
       except GeneratorExit:
           return sum/count
           sum += val
       except BaseException as e:
           exc = e # will be reraised by "yield raise" above
           exc = None
           count += 1

avg = averager()
avg.next() # start coroutine
    avg.send('') # this raises a TypeError at the sum += val line, which is rerouted here by the yield raise
except TypeError:
print avg.close() # still prints 1.5

The above code would be the main use for the feature. However, a side 
benefit would be that a generator that wanted to raise an exception 
instead of closing could use a "yield raise OtherException" as response 
to GeneratorExit. I am not saying we should add the "yield raise" 
feature to the PEP, just that I think this would be a better way to 
handle the "don't close me" cases.
(I am not sure how it would fit into the PEP anyway)

> Greg Ewing wrote:
>> Yield-from would then no longer have the potential
>> to create broken generators, we wouldn't have to treat
>> GeneratorExit differently from any other exception,
>> and Jacob could have his subgenerators that return
>> values when you close them.

Only true because you have redefined it so that no generators are 
broken. If I understand you correctly, you are arguing that this change 
lets us throw GeneratorExit to the subiterator without trying to reraise 
it (my #2 from several mails back). That is clearly a plus in my book 
because it adheres to the inlining principle, but I don't think you need 
the loop in close for it to be better.

Nick Coghlan wrote:
> I think I'd prefer to see some arbitrary limit (500 seems like a nice
> round number) on the number of times that GeneratorExit would be thrown
> before giving up and raising RuntimeError, just so truly broken
> generators that suppressed GeneratorExit in an infinite loop would
> eventually trigger an exception rather than just appearing to hang.

Right. The possibility of turning a call that used to raise a 
RuntimeError into an infinite loop bothers me a bit. I also don't really 
see the use for it. GeneratorExit is an unambiguous signal to close, so 
I would expect the generator to handle it by closing (possibly with a 
final return value), or by raising an exception. Not doing so *should* 
be an error. There has been requests for a function that loops over the 
generator and returns the final result, but this version of close 
doesn't fit that use case because it uses throw(GeneratorExit) instead 
of next().

> The basic idea seems sound though (Jacob's averager example really was
> nicer than mine).

Thank you Nick. I am glad you think so.

To summarize, I am only +0.75 on this proposal. I think it would be 
better not to loop, still return the final value from close, and still 
just throw GeneratorExit to subiterators without trying to reraise.


- Jacob

More information about the Python-ideas mailing list