New examples, explanations and a few edits, all by Chris.
This commit is contained in:
parent
23e1d5b765
commit
89c4b9ef1f
127
pep-0479.txt
127
pep-0479.txt
|
@ -98,6 +98,120 @@ 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.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Generators which explicitly raise StopIteration can generally be
|
||||
changed to simply return instead. This will be compatible with all
|
||||
existing Python versions, and will not be affected by __future__.
|
||||
|
||||
Lib/ipaddress.py::
|
||||
if other == self:
|
||||
raise StopIteration
|
||||
Becomes::
|
||||
if other == self:
|
||||
return
|
||||
|
||||
In some cases, this can be combined with ``yield from`` to simplify
|
||||
the code, such as Lib/difflib.py::
|
||||
if context is None:
|
||||
while True:
|
||||
yield next(line_pair_iterator)
|
||||
Becomes::
|
||||
if context is None:
|
||||
yield from line_pair_iterator
|
||||
return
|
||||
(The ``return`` is necessary for a strictly-equivalent translation,
|
||||
though in this particular file, there is no further code, and the
|
||||
``return`` can be elided.) For compatibility with pre-3.3 versions
|
||||
of Python, this could be written with an explicit ``for`` loop::
|
||||
if context is None:
|
||||
for line in line_pair_iterator:
|
||||
yield line
|
||||
return
|
||||
|
||||
More complicated iteration patterns will need explicit try/catch
|
||||
constructs. For example, a parser construct like this::
|
||||
def unwrap(f):
|
||||
while True:
|
||||
data = next(f)
|
||||
while True:
|
||||
line = next(f)
|
||||
if line == "- end -": break
|
||||
data += line
|
||||
yield data
|
||||
would need to be rewritten as::
|
||||
def parser(f):
|
||||
while True:
|
||||
try:
|
||||
data = next(f)
|
||||
while True:
|
||||
line = next(f)
|
||||
if line == "- end -": break
|
||||
data += line
|
||||
yield data
|
||||
except StopIteration:
|
||||
return
|
||||
or possibly::
|
||||
def parser(f):
|
||||
for data in f:
|
||||
while True:
|
||||
line = next(f)
|
||||
if line == "- end -": break
|
||||
data += line
|
||||
yield data
|
||||
|
||||
The latter form obscures the iteration by purporting to iterate over
|
||||
the file with a ``for`` loop, but then also fetches more data from
|
||||
the same iterator during the loop body. It does, however, clearly
|
||||
differentiate between a "normal" termination (``StopIteration``
|
||||
instead of the initial line) and an "abnormal" termination (failing
|
||||
to find the end marker in the inner loop, which will now raise
|
||||
``RuntimeError``).
|
||||
|
||||
|
||||
Explanation of generators, iterators, and StopIteration
|
||||
=======================================================
|
||||
|
||||
Under this proposal, generators and iterators would be distinct, but
|
||||
related, concepts. Like the mixing of text and bytes in Python 2,
|
||||
the mixing of generators and iterators has resulted in certain
|
||||
perceived conveniences, but proper separation will make bugs more
|
||||
visible.
|
||||
|
||||
An iterator is an object with a ``__next__`` method. Like many other
|
||||
dunder methods, it may either return a value, or raise a specific
|
||||
exception - in this case, ``StopIteration`` - to signal that it has
|
||||
no value to return. In this, it is similar to ``__getattr__`` (can
|
||||
raise ``AttributeError``), ``__getitem__`` (can raise ``KeyError``),
|
||||
and so on. A helper function for an iterator can be written to
|
||||
follow the same protocol; for example::
|
||||
def helper(x, y):
|
||||
if x > y: return 1 / (x - y)
|
||||
raise StopIteration
|
||||
def __next__(self):
|
||||
if self.a: return helper(self.b, self.c)
|
||||
return helper(self.d, self.e)
|
||||
|
||||
Both forms of signalling are carried through: a returned value is
|
||||
returned, an exception bubbles up. The helper is written to match
|
||||
the protocol of the calling function.
|
||||
|
||||
A generator function is one which contains a ``yield`` expression.
|
||||
Each time it is (re)started, it may either yield a value, or return
|
||||
(including "falling off the end"). A helper function for a generator
|
||||
can also be written, but it must also follow generator protocol::
|
||||
def helper(x, y):
|
||||
if x > y: yield 1 / (x - y)
|
||||
def gen(self):
|
||||
if self.a: return (yield from helper(self.b, self.c))
|
||||
return (yield from helper(self.d, self.e))
|
||||
|
||||
In both cases, any unexpected exception will bubble up. Due to the
|
||||
nature of generators and iterators, an unexpected ``StopIteration``
|
||||
inside a generator will be converted into ``RuntimeError``, but
|
||||
beyond that, all exceptions will propagate normally.
|
||||
|
||||
|
||||
Alternate proposals
|
||||
===================
|
||||
|
@ -137,17 +251,20 @@ The inspiration for this alternative proposal was Nick's observation
|
|||
[7]_ that if an ``asyncio`` coroutine [8]_ accidentally raises
|
||||
``StopIteration``, it currently terminates silently, which may present
|
||||
a hard-to-debug mystery to the developer. The main proposal turns
|
||||
such accidents in clearly distinguishable ``RuntimeError`` exceptions,
|
||||
such accidents into clearly distinguishable ``RuntimeError`` exceptions,
|
||||
but if that is rejected, this alternate proposal would enable
|
||||
``asyncio`` to distinguish between a ``return`` statement and an
|
||||
accidentally-raised ``StopIteration`` exception.
|
||||
|
||||
Of the three outcomes listed above:
|
||||
Of the three outcomes listed above, two change:
|
||||
|
||||
* A yielded value, obviously, would still be returned.
|
||||
* If the frame is returned from, ``GeneratorReturn`` is raised.
|
||||
* If a yield point is reached, the value, obviously, would still be
|
||||
returned.
|
||||
* If the frame is returned from, ``GeneratorReturn`` (rather than
|
||||
``StopIteration``) is raised.
|
||||
* If an instance of ``GeneratorReturn`` would be raised, instead an
|
||||
instance of ``StopIteration`` would be raised.
|
||||
instance of ``StopIteration`` would be raised. Any other exception
|
||||
bubbles up normally.
|
||||
|
||||
In the third case, the ``StopIteration`` would have the ``value`` of
|
||||
the original ``GeneratorReturn``, and would reference the original
|
||||
|
|
Loading…
Reference in New Issue