diff --git a/pep-0479.txt b/pep-0479.txt index f1f3ad268..7a63435ea 100644 --- a/pep-0479.txt +++ b/pep-0479.txt @@ -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 =========