diff options
author | Yury Selivanov <yury@magic.io> | 2018-01-26 20:24:24 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-26 20:24:24 (GMT) |
commit | 43c47fe09640c579462978ec16f81295f5857cde (patch) | |
tree | 6a1524a805eb5d181acc04552bfb1a09ea736e39 | |
parent | dba976b8a28d6e5daa66ef31a6a7c694a9193f6a (diff) | |
download | cpython-43c47fe09640c579462978ec16f81295f5857cde.zip cpython-43c47fe09640c579462978ec16f81295f5857cde.tar.gz cpython-43c47fe09640c579462978ec16f81295f5857cde.tar.bz2 |
bpo-32670: Enforce PEP 479. (#5327)
-rw-r--r-- | Doc/library/exceptions.rst | 14 | ||||
-rw-r--r-- | Doc/whatsnew/3.7.rst | 6 | ||||
-rw-r--r-- | Lib/test/test_generators.py | 22 | ||||
-rw-r--r-- | Lib/test/test_with.py | 11 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2018-01-25-17-03-46.bpo-32670.YsqJUC.rst | 5 | ||||
-rw-r--r-- | Objects/genobject.c | 58 | ||||
-rw-r--r-- | Python/future.c | 2 |
7 files changed, 38 insertions, 80 deletions
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) { |