Another major editing pass. Move __future__ into spec. Add section about compatible code.

This commit is contained in:
Guido van Rossum 2014-11-25 22:05:29 -08:00
parent 5c96ac2382
commit a2bb199d1d
1 changed files with 47 additions and 28 deletions

View File

@ -14,10 +14,12 @@ Post-History: 15-Nov-2014, 19-Nov-2014
Abstract Abstract
======== ========
This PEP proposes a backwards incompatible change to generators: when This PEP proposes a change to generators: when ``StopIteration`` is
``StopIteration`` is raised inside a generator, it is replaced it with raised inside a generator, it is replaced it with ``RuntimeError``.
``RuntimeError``. (More precisely, this happens when the exception is (More precisely, this happens when the exception is about to bubble
about to bubble out of the generator's stack frame.) out of the generator's stack frame.) Because the change is backwards
incompatible, the feature is initially introduced using a
``__future__`` statement.
Rationale Rationale
@ -100,6 +102,17 @@ a generator, if the generator doesn't catch it (which it could do
using a ``try/except`` around the ``yield``), it will be transformed using a ``try/except`` around the ``yield``), it will be transformed
into ``RuntimeError``. into ``RuntimeError``.
During the transition phase, the new feature must be enabled
per-module using::
from __future__ import generator_stop
Any generator function constructed under the influence of this
directive will have the ``REPLACE_STOPITERATION`` 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.
Consequences for existing code Consequences for existing code
============================== ==============================
@ -116,28 +129,34 @@ function that terminated the generator you'd have to do "return
yield from helper()" rather than just "helper()".""") yield from helper()" rather than just "helper()".""")
There are also examples of generator expressions floating around that There are also examples of generator expressions floating around that
rely on a StopIteration raised by the expression, the target or the rely on a ``StopIteration`` raised by the expression, the target or the
predicate (rather than by the __next__() call implied in the ``for`` predicate (rather than by the ``__next__()`` call implied in the ``for``
loop proper). loop proper).
As this can break code, it is proposed to utilize the ``__future__`` Writing backwards and forwards compatible code
mechanism to introduce this in Python 3.5, finally making it standard ----------------------------------------------
in Python 3.6 or 3.7. The proposed syntax is::
from __future__ import generator_stop With the exception of hacks that raise ``StopIteration`` to exit a
generator expression, it is easy to write code that works equally well
under older Python versions as under the new semantics.
Any generator function constructed under the influence of this This is done by enclosing those places in the generator body where a
directive will have the REPLACE_STOPITERATION flag set on its code ``StopIteration`` is expected (e.g. bare ``next()`` calls or in some
object, and generators with the flag set will behave according to this cases helper functions that are expected to raise ``StopIteration``)
proposal. Once the feature becomes standard, the flag may be dropped; in a ``try/except`` construct that returns when ``StopIteration``
code should not inspect generators for it. returns. The ``try/except`` construct should appear directly in the
generator function; doing this in a helper function that is not itself
a generator does not work. If ``raise StopIteration`` occurs directly
in a generator, simply replace it with ``return``.
Examples
--------
Generators which explicitly raise StopIteration can generally be Examples of breakage
--------------------
Generators which explicitly raise ``StopIteration`` can generally be
changed to simply return instead. This will be compatible with all changed to simply return instead. This will be compatible with all
existing Python versions, and will not be affected by __future__. existing Python versions, and will not be affected by ``__future__``.
Here are some illustrations from the standard library.
Lib/ipaddress.py:: Lib/ipaddress.py::
@ -164,7 +183,7 @@ Becomes::
(The ``return`` is necessary for a strictly-equivalent translation, (The ``return`` is necessary for a strictly-equivalent translation,
though in this particular file, there is no further code, and the though in this particular file, there is no further code, and the
``return`` can be elided.) For compatibility with pre-3.3 versions ``return`` can be omitted.) For compatibility with pre-3.3 versions
of Python, this could be written with an explicit ``for`` loop:: of Python, this could be written with an explicit ``for`` loop::
if context is None: if context is None:
@ -172,10 +191,10 @@ of Python, this could be written with an explicit ``for`` loop::
yield line yield line
return return
More complicated iteration patterns will need explicit try/catch More complicated iteration patterns will need explicit ``try/except``
constructs. For example, a parser construct like this:: constructs. For example, a hypothetical parser like this::
def unwrap(f): def parser(f):
while True: while True:
data = next(f) data = next(f)
while True: while True:
@ -227,7 +246,7 @@ perceived conveniences, but proper separation will make bugs more
visible. visible.
An iterator is an object with a ``__next__`` method. Like many other An iterator is an object with a ``__next__`` method. Like many other
dunder methods, it may either return a value, or raise a specific special methods, it may either return a value, or raise a specific
exception - in this case, ``StopIteration`` - to signal that it has exception - in this case, ``StopIteration`` - to signal that it has
no value to return. In this, it is similar to ``__getattr__`` (can no value to return. In this, it is similar to ``__getattr__`` (can
raise ``AttributeError``), ``__getitem__`` (can raise ``KeyError``), raise ``AttributeError``), ``__getitem__`` (can raise ``KeyError``),
@ -271,9 +290,9 @@ Transition plan
deprecation warning if ``StopIteration`` bubbles out of a generator deprecation warning if ``StopIteration`` bubbles out of a generator
not under ``__future__`` import. not under ``__future__`` import.
- Python 3.6: non-silent deprecation warning. - Python 3.6: Non-silent deprecation warning.
- Python 3.7: enable new semantics everywhere. - Python 3.7: Enable new semantics everywhere.
Alternate proposals Alternate proposals
@ -363,12 +382,12 @@ The existing model has been compared to the perfectly-acceptable
issues inherent to every other case where an exception has special issues inherent to every other case where an exception has special
meaning. For instance, an unexpected ``KeyError`` inside a meaning. For instance, an unexpected ``KeyError`` inside a
``__getitem__`` method will be interpreted as failure, rather than ``__getitem__`` method will be interpreted as failure, rather than
permitted to bubble up. However, there is a difference. Dunder permitted to bubble up. However, there is a difference. Special
methods use ``return`` to indicate normality, and ``raise`` to signal methods use ``return`` to indicate normality, and ``raise`` to signal
abnormality; generators ``yield`` to indicate data, and ``return`` to abnormality; generators ``yield`` to indicate data, and ``return`` to
signal the abnormal state. This makes explicitly raising signal the abnormal state. This makes explicitly raising
``StopIteration`` entirely redundant, and potentially surprising. If ``StopIteration`` entirely redundant, and potentially surprising. If
other dunder methods had dedicated keywords to distinguish between other special methods had dedicated keywords to distinguish between
their return paths, they too could turn unexpected exceptions into their return paths, they too could turn unexpected exceptions into
``RuntimeError``; the fact that they cannot should not preclude ``RuntimeError``; the fact that they cannot should not preclude
generators from doing so. generators from doing so.