[Python-checkins] cpython: Issue 24237: Raise PendingDeprecationWarning per PEP 479

yury.selivanov python-checkins at python.org
Fri May 22 17:16:54 CEST 2015


https://hg.python.org/cpython/rev/2771a0ac806b
changeset:   96206:2771a0ac806b
user:        Yury Selivanov <yselivanov at sprymix.com>
date:        Fri May 22 11:16:47 2015 -0400
summary:
  Issue 24237: Raise PendingDeprecationWarning per PEP 479

Raise PendingDeprecationWarning when generator raises StopIteration
and no __future__ import is used.  Fix offenders in the stdlib
and tests.

See also issue 22906.
Thanks to Nick Coghlan and Berker Peksag for reviews.

files:
  Lib/difflib.py              |  10 +++-
  Lib/test/test_contextlib.py |   6 +-
  Lib/test/test_generators.py |  62 ++++++++++++++++--------
  Lib/test/test_with.py       |   6 +-
  Objects/genobject.c         |  23 ++++++++-
  5 files changed, 78 insertions(+), 29 deletions(-)


diff --git a/Lib/difflib.py b/Lib/difflib.py
--- a/Lib/difflib.py
+++ b/Lib/difflib.py
@@ -1582,7 +1582,10 @@
         while True:
             # Collecting lines of text until we have a from/to pair
             while (len(fromlines)==0 or len(tolines)==0):
-                from_line, to_line, found_diff = next(line_iterator)
+                try:
+                    from_line, to_line, found_diff = next(line_iterator)
+                except StopIteration:
+                    return
                 if from_line is not None:
                     fromlines.append((from_line,found_diff))
                 if to_line is not None:
@@ -1609,7 +1612,10 @@
             index, contextLines = 0, [None]*(context)
             found_diff = False
             while(found_diff is False):
-                from_line, to_line, found_diff = next(line_pair_iterator)
+                try:
+                    from_line, to_line, found_diff = next(line_pair_iterator)
+                except StopIteration:
+                    return
                 i = index % context
                 contextLines[i] = (from_line, to_line, found_diff)
                 index += 1
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -89,8 +89,10 @@
         def woohoo():
             yield
         try:
-            with woohoo():
-                raise stop_exc
+            with self.assertWarnsRegex(PendingDeprecationWarning,
+                                       "StopIteration"):
+                with woohoo():
+                    raise stop_exc
         except Exception as ex:
             self.assertIs(ex, stop_exc)
         else:
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -1,6 +1,7 @@
 import gc
 import sys
 import unittest
+import warnings
 import weakref
 
 from test import support
@@ -217,6 +218,46 @@
         self.assertEqual(next(g), "done")
         self.assertEqual(sys.exc_info(), (None, None, None))
 
+    def test_stopiteration_warning(self):
+        # See also PEP 479.
+
+        def gen():
+            raise StopIteration
+            yield
+
+        with self.assertRaises(StopIteration), \
+             self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
+
+            next(gen())
+
+        with self.assertRaisesRegex(PendingDeprecationWarning,
+                                    "generator .* raised StopIteration"), \
+             warnings.catch_warnings():
+
+            warnings.simplefilter('error')
+            next(gen())
+
+
+    def test_tutorial_stopiteration(self):
+        # Raise StopIteration" stops the generator too:
+
+        def f():
+            yield 1
+            raise StopIteration
+            yield 2 # never reached
+
+        g = f()
+        self.assertEqual(next(g), 1)
+
+        with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
+            with self.assertRaises(StopIteration):
+                next(g)
+
+        with self.assertRaises(StopIteration):
+            # This time StopIteration isn't raised from the generator's body,
+            # hence no warning.
+            next(g)
+
 
 tutorial_tests = """
 Let's try a simple generator:
@@ -263,26 +304,7 @@
       File "<stdin>", line 1, in ?
     StopIteration
 
-"raise StopIteration" stops the generator too:
-
-    >>> def f():
-    ...     yield 1
-    ...     raise StopIteration
-    ...     yield 2 # never reached
-    ...
-    >>> g = f()
-    >>> next(g)
-    1
-    >>> next(g)
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in ?
-    StopIteration
-    >>> next(g)
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in ?
-    StopIteration
-
-However, they are not exactly equivalent:
+However, "return" and StopIteration are not exactly equivalent:
 
     >>> def g1():
     ...     try:
diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py
--- a/Lib/test/test_with.py
+++ b/Lib/test/test_with.py
@@ -454,7 +454,8 @@
             with cm():
                 raise StopIteration("from with")
 
-        self.assertRaises(StopIteration, shouldThrow)
+        with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
+            self.assertRaises(StopIteration, shouldThrow)
 
     def testRaisedStopIteration2(self):
         # From bug 1462485
@@ -481,7 +482,8 @@
             with cm():
                 raise next(iter([]))
 
-        self.assertRaises(StopIteration, shouldThrow)
+        with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
+            self.assertRaises(StopIteration, shouldThrow)
 
     def testRaisedGeneratorExit1(self):
         # From bug 1462485
diff --git a/Objects/genobject.c b/Objects/genobject.c
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -143,13 +143,12 @@
         }
         Py_CLEAR(result);
     }
-    else if (!result) {
+    else if (!result && PyErr_ExceptionMatches(PyExc_StopIteration)) {
         /* Check for __future__ generator_stop and conditionally turn
          * a leaking StopIteration into RuntimeError (with its cause
          * set appropriately). */
-        if ((((PyCodeObject *)gen->gi_code)->co_flags &
+        if (((PyCodeObject *)gen->gi_code)->co_flags &
               (CO_FUTURE_GENERATOR_STOP | CO_COROUTINE | CO_ITERABLE_COROUTINE))
-            && PyErr_ExceptionMatches(PyExc_StopIteration))
         {
             PyObject *exc, *val, *val2, *tb;
             PyErr_Fetch(&exc, &val, &tb);
@@ -167,6 +166,24 @@
             PyException_SetContext(val2, val);
             PyErr_Restore(exc, val2, tb);
         }
+        else {
+            PyObject *exc, *val, *tb;
+
+            /* Pop the exception before issuing a warning. */
+            PyErr_Fetch(&exc, &val, &tb);
+
+            if (PyErr_WarnFormat(PyExc_PendingDeprecationWarning, 1,
+                                 "generator '%.50S' raised StopIteration",
+                                 gen->gi_qualname)) {
+                /* Warning was converted to an error. */
+                Py_XDECREF(exc);
+                Py_XDECREF(val);
+                Py_XDECREF(tb);
+            }
+            else {
+                PyErr_Restore(exc, val, tb);
+            }
+        }
     }
 
     if (!result || f->f_stacktop == NULL) {

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list