[Python-checkins] peps: New examples, explanations and a few edits, all by Chris.
guido.van.rossum
python-checkins at python.org
Fri Nov 21 06:14:23 CET 2014
https://hg.python.org/peps/rev/43d99a738fa1
changeset: 5608:43d99a738fa1
user: Guido van Rossum <guido at python.org>
date: Thu Nov 20 21:07:18 2014 -0800
summary:
New examples, explanations and a few edits, all by Chris.
files:
pep-0479.txt | 127 +++++++++++++++++++++++++++++++++++++-
1 files changed, 122 insertions(+), 5 deletions(-)
diff --git a/pep-0479.txt b/pep-0479.txt
--- a/pep-0479.txt
+++ b/pep-0479.txt
@@ -98,6 +98,120 @@
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 @@
[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
--
Repository URL: https://hg.python.org/peps
More information about the Python-checkins
mailing list