[Python-ideas] Possible PEP 380 tweak

Jacob Holm jh at improva.dk
Thu Oct 28 10:52:53 CEST 2010


On 2010-10-28 00:52, Nick Coghlan wrote:
> On Thu, Oct 28, 2010 at 6:22 AM, Jacob Holm <jh at improva.dk> wrote:
>> Actually, AFAICT outer_broken will *not* give a RuntimeError on close()
>> after next().  This is due to the special-casing of GeneratorExit in PEP
>> 380.  That special-casing is also the basis for both my suggested
>> modifications.
> 
> Ah, you're quite right - I'd completely forgotten about the
> GeneratorExit special-casing in the PEP 380 semantics, so I was
> arguing from a faulty premise. With that error corrected, I can
> happily withdraw my objection to idioms that convert GeneratorExit to
> StopIteration (since any yield from expressions will reraise the
> GeneratorExit in that case).
> 

Looks like we are still not on exactly the same page though...  You seem
to be arguing from the version at
http://www.python.org/dev/peps/pep-0380, whereas I am looking at
http://mail.python.org/pipermail/python-ideas/attachments/20090419/c7d72ba8/attachment-0001.txt,
which is newer.



> The "did-it-really-finish?" question can likely be answered by
> slightly improving generator state introspection from the Python level
> (as I believe Guido suggested earlier in the thread). That way close()
> can keep the gist of its current semantics (return something if the
> generator ends up in an inactive state, raise RuntimeError if it
> yields another value), while frameworks can object to other unexpected
> states if they want to.
> 
> As it turns out, the information on generator state is already there,
> just not in a particularly user friendly format ("not started" =
> "g.gi_frame is not None and g.gi_frame.f_lasti == -1", "terminated" =
> "g.gi_frame is None").
> 
> So, without any modifications at all to the current incarnation of PEP
> 380, it is already possible to write:
> 
> def finish(gen):
>    frame = gen.gi_frame
>    if frame is None:
>        raise RuntimeError('finish() on exhausted/closed generator')
>    if frame.f_lasti == -1:
>        raise RuntimeError('finish() on not yet started generator')
>    try:
>        gen.throw(GeneratorExit)
>    except StopIteration as err:
>        if err.args:
>            return err.args[0]
>        return None
>    except GeneratorExit:
>        pass
>    else:
>        raise RuntimeError('Generator ignored GeneratorExit')
>    raise RuntimeError('Generator failed to return a value')
> 

Yes.  I forgot about the "not yet started" case in my earlier versions.



> I think I'm finally starting to understand *your* question/concern
> though. Given the current PEP 380 expansion, the above definition of
> finish() and the following two generators:
> 
> def g_inner():
>   yield
>   return "Hello world!"
> 
> def g_outer():
>   yield (yield from g_inner())
> 
> You would get the following result (as g_inner converts GeneratorExit
> to StopIteration, then yield from propogates that up the stack):
>>>> g = g_outer()
>>>> next(g)
>>>> finish(g)
> "Hello world!"
> 
> Oops?
> 

Well.  Not with the newest expansion.  Not that the None you will get
from that one is any better.


> I'm wondering if this part of the PEP 380 expansion:
>                     if _e is _x[1] or isinstance(_x[1], GeneratorExit):
>                         raise
> 
> Should actually look like:
>                     if _e is _x[1]:
>                         raise
>                     if isinstance(_x[1], GeneratorExit):
>                         raise GeneratorExit(*_e.args)
> 

In the newer expansion, I would change:

            except GeneratorExit as _e:
                try:
                    _m = getattr(_i, 'close')
                except AttributeError:
                    pass
                else:
                    _m()
                raise _e

Into:

            except GeneratorExit as _e:
                try:
                    _m = getattr(_i, 'close')
                except AttributeError:
                    pass
                else:
                    raise GeneratorExit(_m())
                raise _e

(Which can cleaned up a bit btw., by removing _e and using direct
attribute access instead of getattr)

> Once that distinction is made, you can more easily write helper
> functions and context managers that allow code to do the "right thing"
> according to the needs of a particular framework or application.
> 

Yes.  OTOH, I have argued for this change before with no luck.

- Jacob



More information about the Python-ideas mailing list