[Python-checkins] bpo-32670: Enforce PEP 479. (#5327)

Yury Selivanov webhook-mailer at python.org
Fri Jan 26 15:24:32 EST 2018


https://github.com/python/cpython/commit/43c47fe09640c579462978ec16f81295f5857cde
commit: 43c47fe09640c579462978ec16f81295f5857cde
branch: master
author: Yury Selivanov <yury at magic.io>
committer: GitHub <noreply at github.com>
date: 2018-01-26T15:24:24-05:00
summary:

bpo-32670: Enforce PEP 479. (#5327)

files:
A Misc/NEWS.d/next/Core and Builtins/2018-01-25-17-03-46.bpo-32670.YsqJUC.rst
M Doc/library/exceptions.rst
M Doc/whatsnew/3.7.rst
M Lib/test/test_generators.py
M Lib/test/test_with.py
M Objects/genobject.c
M Python/future.c

diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst
index aa3141053b3..42b99cc47e0 100644
--- a/Doc/library/exceptions.rst
+++ b/Doc/library/exceptions.rst
@@ -367,17 +367,21 @@ The following exceptions are the exceptions that are usually raised.
    raised, and the value returned by the function is used as the
    :attr:`value` parameter to the constructor of the exception.
 
-   If a generator function defined in the presence of a ``from __future__
-   import generator_stop`` directive raises :exc:`StopIteration`, it will be
-   converted into a :exc:`RuntimeError` (retaining the :exc:`StopIteration`
-   as the new exception's cause).
+   If a generator code directly or indirectly raises :exc:`StopIteration`,
+   it is converted into a :exc:`RuntimeError` (retaining the
+   :exc:`StopIteration` as the new exception's cause).
 
    .. versionchanged:: 3.3
       Added ``value`` attribute and the ability for generator functions to
       use it to return a value.
 
    .. versionchanged:: 3.5
-      Introduced the RuntimeError transformation.
+      Introduced the RuntimeError transformation via
+      ``from __future__ import generator_stop``, see :pep:`479`.
+
+   .. versionchanged:: 3.7
+      Enable :pep:`479` for all code by default: a :exc:`StopIteration`
+      error raised in a generator is transformed into a :exc:`RuntimeError`.
 
 .. exception:: StopAsyncIteration
 
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index a350919bb34..43fbd013856 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -935,6 +935,12 @@ that may require changes to your code.
 Changes in Python behavior
 --------------------------
 
+* :pep:`479` is enabled for all code in Python 3.7, meaning that
+  :exc:`StopIteration` exceptions raised directly or indirectly in
+  coroutines and generators are transformed into :exc:`RuntimeError`
+  exceptions.
+  (Contributed by Yury Selivanov in :issue:`32670`.)
+
 * Due to an oversight, earlier Python versions erroneously accepted the
   following syntax::
 
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index bd17ad4a21b..3461f203dee 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -265,26 +265,16 @@ def gen():
         self.assertEqual(next(g), "done")
         self.assertEqual(sys.exc_info(), (None, None, None))
 
-    def test_stopiteration_warning(self):
+    def test_stopiteration_error(self):
         # See also PEP 479.
 
         def gen():
             raise StopIteration
             yield
 
-        with self.assertRaises(StopIteration), \
-             self.assertWarnsRegex(DeprecationWarning, "StopIteration"):
-
-            next(gen())
-
-        with self.assertRaisesRegex(DeprecationWarning,
-                                    "generator .* raised StopIteration"), \
-             warnings.catch_warnings():
-
-            warnings.simplefilter('error')
+        with self.assertRaisesRegex(RuntimeError, 'raised StopIteration'):
             next(gen())
 
-
     def test_tutorial_stopiteration(self):
         # Raise StopIteration" stops the generator too:
 
@@ -296,13 +286,7 @@ def f():
         g = f()
         self.assertEqual(next(g), 1)
 
-        with self.assertWarnsRegex(DeprecationWarning, "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.
+        with self.assertRaisesRegex(RuntimeError, 'raised StopIteration'):
             next(g)
 
     def test_return_tuple(self):
diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py
index c70f6859b4b..b1d7a15b5e4 100644
--- a/Lib/test/test_with.py
+++ b/Lib/test/test_with.py
@@ -458,8 +458,8 @@ def shouldThrow():
             with cm():
                 raise StopIteration("from with")
 
-        with self.assertWarnsRegex(DeprecationWarning, "StopIteration"):
-            self.assertRaises(StopIteration, shouldThrow)
+        with self.assertRaisesRegex(StopIteration, 'from with'):
+            shouldThrow()
 
     def testRaisedStopIteration2(self):
         # From bug 1462485
@@ -473,7 +473,8 @@ def shouldThrow():
             with cm():
                 raise StopIteration("from with")
 
-        self.assertRaises(StopIteration, shouldThrow)
+        with self.assertRaisesRegex(StopIteration, 'from with'):
+            shouldThrow()
 
     def testRaisedStopIteration3(self):
         # Another variant where the exception hasn't been instantiated
@@ -486,8 +487,8 @@ def shouldThrow():
             with cm():
                 raise next(iter([]))
 
-        with self.assertWarnsRegex(DeprecationWarning, "StopIteration"):
-            self.assertRaises(StopIteration, shouldThrow)
+        with self.assertRaises(StopIteration):
+            shouldThrow()
 
     def testRaisedGeneratorExit1(self):
         # From bug 1462485
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-01-25-17-03-46.bpo-32670.YsqJUC.rst b/Misc/NEWS.d/next/Core and Builtins/2018-01-25-17-03-46.bpo-32670.YsqJUC.rst
new file mode 100644
index 00000000000..b22249b37e5
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2018-01-25-17-03-46.bpo-32670.YsqJUC.rst	
@@ -0,0 +1,5 @@
+Enforce PEP 479 for all code.
+
+This means that manually raising a StopIteration exception from a generator
+is prohibited for all code, regardless of whether 'from __future__ import
+generator_stop' was used or not.
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 7baffa7fc02..1fdb57c8ce8 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -248,59 +248,17 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
         Py_CLEAR(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). */
-
-        const int check_stop_iter_error_flags = CO_FUTURE_GENERATOR_STOP |
-                                                CO_COROUTINE |
-                                                CO_ITERABLE_COROUTINE |
-                                                CO_ASYNC_GENERATOR;
-
-        if (gen->gi_code != NULL &&
-            ((PyCodeObject *)gen->gi_code)->co_flags &
-                check_stop_iter_error_flags)
-        {
-            /* `gen` is either:
-                  * a generator with CO_FUTURE_GENERATOR_STOP flag;
-                  * a coroutine;
-                  * a generator with CO_ITERABLE_COROUTINE flag
-                    (decorated with types.coroutine decorator);
-                  * an async generator.
-            */
-            const char *msg = "generator raised StopIteration";
-            if (PyCoro_CheckExact(gen)) {
-                msg = "coroutine raised StopIteration";
-            }
-            else if PyAsyncGen_CheckExact(gen) {
-                msg = "async generator raised StopIteration";
-            }
-            _PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
+        const char *msg = "generator raised StopIteration";
+        if (PyCoro_CheckExact(gen)) {
+            msg = "coroutine raised StopIteration";
         }
-        else {
-            /* `gen` is an ordinary generator without
-               CO_FUTURE_GENERATOR_STOP flag.
-            */
-
-            PyObject *exc, *val, *tb;
-
-            /* Pop the exception before issuing a warning. */
-            PyErr_Fetch(&exc, &val, &tb);
-
-            if (PyErr_WarnFormat(PyExc_DeprecationWarning, 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);
-            }
+        else if PyAsyncGen_CheckExact(gen) {
+            msg = "async generator raised StopIteration";
         }
+        _PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
+
     }
-    else if (PyAsyncGen_CheckExact(gen) && !result &&
+    else if (!result && PyAsyncGen_CheckExact(gen) &&
              PyErr_ExceptionMatches(PyExc_StopAsyncIteration))
     {
         /* code in `gen` raised a StopAsyncIteration error:
diff --git a/Python/future.c b/Python/future.c
index 53faa6b9016..ade647f25ba 100644
--- a/Python/future.c
+++ b/Python/future.c
@@ -41,7 +41,7 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
         } else if (strcmp(feature, FUTURE_BARRY_AS_BDFL) == 0) {
             ff->ff_features |= CO_FUTURE_BARRY_AS_BDFL;
         } else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) {
-            ff->ff_features |= CO_FUTURE_GENERATOR_STOP;
+            continue;
         } else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) {
             ff->ff_features |= CO_FUTURE_ANNOTATIONS;
         } else if (strcmp(feature, "braces") == 0) {



More information about the Python-checkins mailing list