[Python-ideas] Yield-From: GeneratorExit?

Jacob Holm jh at improva.dk
Mon Mar 23 01:07:12 CET 2009


Hi Nick

Nick Coghlan wrote:
> Jacob Holm wrote:
>   
>> If I understand Nick correctly, he would like to drop the "except
>> GeneratorExit: raise" part, and possibly change BaseException to
>> Exception. I don't like the idea of just dropping the "except
>> GeneratorExit: raise", as that brings us back in the situation where
>> shared subiterators are less useful. If we also change BaseException to
>> Exception, the only difference is that it will no longer be possible to
>> throw exceptions like SystemExit and KeyboardInterrupt that don't
>> inherit from Exception to a subiterator.
>>     
>
> Note that as of 2.6, GeneratorExit doesn't inherit from Exception either
> - it now inherits directly from BaseException, just like the other two
> terminal exceptions:
>   
I know this.

> All I'm saying is that if GeneratorExit doesn't get passed down then
> neither should SystemExit nor KeyboardInterrupt, while if the latter two
> *do* get passed down, then so should GeneratorExit.
>   
I also know this, and I disagree.  You are saying that because they have 
the thing in commen that they do *not* inherit from Exception we should 
treat them the same.  This is like saying that anything that is not a 
shade of green should be treated as red, completely ignoring the 
possibility of other colors.

I like to see GeneratorExit handled as a special case by yield-from, 
because:

   1. It already has a special meaning in generators as the exception
      raised in the generator when close is called.
   2. It *enables* certain uses of yield-from that would require much
      more more work to handle otherwise.  I am thinking of the ability
      to have multiple generators yield from the same iterator.  Being
      able to close one generator without closing the shared iterator
      seems like a good thing.
   3. While the GeneratorExit is not propagated directly, its expected
      effect of finalizing the subiterator *is*. At least in CPython,
      and assuming the subiterator does its finalization in a __del__
      method, and that the generator holds the only reference. If the
      subiterator is actually a generator, it will even look like the
      GeneratorExit was propagated, due to the PEP 342 definition of close.

I don't like the idea of only throwing exceptions that inherit from 
Exception to the subiterator, because it makes the following two 
generators behave differently when thrown a non-Exception exception.

def generatorA():
    try:
        x = yield
    except BaseException, e:
        print type(e)
        raise

def generatorB():
    return (yield from generatorA())


The PEP is clearly intended to make them act identically.  Quoting from 
the PEP: "When the iterator is another generator, the effect is the same 
as if the body of the subgenerator were inlined at the point of the 
yield from expression".

Treating only GeneratorExit special allows them to behave exactly the 
same (in CPython).  If you only propagate exceptions that inherit from 
Exception, you would have to write something like:

def generatorC():
    g = generatorA()
    while 1:
        try:
            return (yield from g)
        except Exception:
            # This exception comes from g, so just reraise
            raise
        except BaseException, e:
            yield g.throw(e) # this exception was not propagated by yield-from, do it manually


to get the same effect.

I don't mind that the expansion as written in the PEP becomes very 
slightly more complicated, as long as it makes the code  using it 
simpler to reason about.

- Jacob



More information about the Python-ideas mailing list