Update PEP 479 from author.

This commit is contained in:
Georg Brandl 2014-11-17 10:26:09 +01:00
parent 4dfd824041
commit 66080c2e52
1 changed files with 55 additions and 7 deletions

View File

@ -29,15 +29,27 @@ cause a noisy and easily-debugged traceback. Currently,
``StopIteration`` can be absorbed by the generator construct.
Background information
======================
When a generator frame is (re)started as a result of a ``__next__()``
(or ``send()`` or ``throw()``) call, one of three outcomes can occur:
* A yield point is reached, and the yielded value is returned.
* The frame is returned from; ``StopIteration`` is raised.
* An exception is thrown, which bubbles out.
Proposal
========
If a ``StopIteration`` is about to bubble out of a generator frame, it
is replaced with some other exception (maybe ``RuntimeError``, maybe a
new custom ``Exception`` subclass, but *not* deriving from
``StopIteration``) which causes the ``next()`` call (which invoked the
generator) to fail, passing that exception out. From then on it's
just like any old exception. [3]_
is replaced with ``RuntimeError``, which causes the ``next()`` call
(which invoked the generator) to fail, passing that exception out.
From then on it's just like any old exception. [3]_
This affects the third outcome listed above, without altering any
other effects.
Consequences to existing code
@ -56,19 +68,38 @@ yield from helper()" rather than just "helper()".""")
As this can break code, it is proposed to utilize the ``__future__``
mechanism to introduce this, finally making it standard in Python 3.6
or 3.7.
or 3.7. Any generator function constructed in the presence of this
directive will have a flag set on its code object, and generators with
the flag set will behave according to this proposal. Once the feature
becomes standard, the flag may be dropped; code should not inspect
generators for it. (GvR: """And the flag should somehow be
transferred to the stack frame when the function is executed, so the
right action can be taken when an exception is about to bubble out of
that frame.""")
Alternate proposals
===================
Raising something other than RuntimeError
-----------------------------------------
Rather than the generic ``RuntimeError``, it might make sense to raise
a new exception type ``UnexpectedStopIteration``. This has the
downside of implicitly encouraging that it be caught; the correct
action is to catch the original ``StopIteration``, not the chained
exception.
Supplying a specific exception to raise on return
-------------------------------------------------
Nick Coghlan suggested a means of providing a specific
``StopIteration`` instance to the generator; if any other instance of
``StopIteration`` is raised, it is an error, but if that particular
one is raised, the generator has properly completed.
one is raised, the generator has properly completed. This subproposal
has been withdrawn in favour of better options, but is retained for
reference.
Making return-triggered StopIterations obvious
@ -80,6 +111,23 @@ raising ``StopIteration``, it raises a specific subclass of
``StopIteration`` which can then be detected. If it is not that
subclass, it is an escaping exception rather than a return statement.
Of the three outcomes listed above:
* A yielded value, obviously, would still be returned.
* If the frame is returned from, ``GeneratorReturn`` is raised.
* If an instance of ``GeneratorReturn`` would be raised, instead an
instance of ``StopIteration`` would be raised.
In the third case, the ``StopIteration`` would have the ``value`` of
the original ``GeneratorReturn``, and would reference the original
exception in its ``__cause__``. If uncaught, this would clearly show
the chaining of exceptions.
This does *not* affect the discrepancy between generator expressions
and list comprehensions, but allows generator-aware code (such as the
contextlib and asyncio modules) to reliably differentiate between the
second and third outcomes listed above.
Criticism
=========