From 43c47fe09640c579462978ec16f81295f5857cde Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 26 Jan 2018 15:24:24 -0500 Subject: bpo-32670: Enforce PEP 479. (#5327) --- Doc/library/exceptions.rst | 14 ++++-- Doc/whatsnew/3.7.rst | 6 +++ Lib/test/test_generators.py | 22 ++------ Lib/test/test_with.py | 11 ++-- .../2018-01-25-17-03-46.bpo-32670.YsqJUC.rst | 5 ++ Objects/genobject.c | 58 +++------------------- Python/future.c | 2 +- 7 files changed, 38 insertions(+), 80 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-01-25-17-03-46.bpo-32670.YsqJUC.rst diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index aa31410..42b99cc 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 a350919..43fbd01 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 bd17ad4..3461f20 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -265,26 +265,16 @@ class ExceptionTest(unittest.TestCase): 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 @@ class ExceptionTest(unittest.TestCase): 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 c70f685..b1d7a15 100644 --- a/Lib/test/test_with.py +++ b/Lib/test/test_with.py @@ -458,8 +458,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase): 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 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase): 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 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase): 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 0000000..b22249b --- /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 7baffa7..1fdb57c 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 53faa6b..ade647f 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) { -- cgit v0.12