[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