Unverified Kaydet (Commit) 43c47fe0 authored tarafından Yury Selivanov's avatar Yury Selivanov Kaydeden (comit) GitHub

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

üst dba976b8
...@@ -367,17 +367,21 @@ The following exceptions are the exceptions that are usually raised. ...@@ -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 raised, and the value returned by the function is used as the
:attr:`value` parameter to the constructor of the exception. :attr:`value` parameter to the constructor of the exception.
If a generator function defined in the presence of a ``from __future__ If a generator code directly or indirectly raises :exc:`StopIteration`,
import generator_stop`` directive raises :exc:`StopIteration`, it will be it is converted into a :exc:`RuntimeError` (retaining the
converted into a :exc:`RuntimeError` (retaining the :exc:`StopIteration` :exc:`StopIteration` as the new exception's cause).
as the new exception's cause).
.. versionchanged:: 3.3 .. versionchanged:: 3.3
Added ``value`` attribute and the ability for generator functions to Added ``value`` attribute and the ability for generator functions to
use it to return a value. use it to return a value.
.. versionchanged:: 3.5 .. 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 .. exception:: StopAsyncIteration
......
...@@ -935,6 +935,12 @@ that may require changes to your code. ...@@ -935,6 +935,12 @@ that may require changes to your code.
Changes in Python behavior 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 * Due to an oversight, earlier Python versions erroneously accepted the
following syntax:: following syntax::
......
...@@ -265,26 +265,16 @@ class ExceptionTest(unittest.TestCase): ...@@ -265,26 +265,16 @@ class ExceptionTest(unittest.TestCase):
self.assertEqual(next(g), "done") self.assertEqual(next(g), "done")
self.assertEqual(sys.exc_info(), (None, None, None)) self.assertEqual(sys.exc_info(), (None, None, None))
def test_stopiteration_warning(self): def test_stopiteration_error(self):
# See also PEP 479. # See also PEP 479.
def gen(): def gen():
raise StopIteration raise StopIteration
yield yield
with self.assertRaises(StopIteration), \ with self.assertRaisesRegex(RuntimeError, 'raised StopIteration'):
self.assertWarnsRegex(DeprecationWarning, "StopIteration"):
next(gen())
with self.assertRaisesRegex(DeprecationWarning,
"generator .* raised StopIteration"), \
warnings.catch_warnings():
warnings.simplefilter('error')
next(gen()) next(gen())
def test_tutorial_stopiteration(self): def test_tutorial_stopiteration(self):
# Raise StopIteration" stops the generator too: # Raise StopIteration" stops the generator too:
...@@ -296,13 +286,7 @@ class ExceptionTest(unittest.TestCase): ...@@ -296,13 +286,7 @@ class ExceptionTest(unittest.TestCase):
g = f() g = f()
self.assertEqual(next(g), 1) self.assertEqual(next(g), 1)
with self.assertWarnsRegex(DeprecationWarning, "StopIteration"): with self.assertRaisesRegex(RuntimeError, 'raised 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) next(g)
def test_return_tuple(self): def test_return_tuple(self):
......
...@@ -458,8 +458,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase): ...@@ -458,8 +458,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase):
with cm(): with cm():
raise StopIteration("from with") raise StopIteration("from with")
with self.assertWarnsRegex(DeprecationWarning, "StopIteration"): with self.assertRaisesRegex(StopIteration, 'from with'):
self.assertRaises(StopIteration, shouldThrow) shouldThrow()
def testRaisedStopIteration2(self): def testRaisedStopIteration2(self):
# From bug 1462485 # From bug 1462485
...@@ -473,7 +473,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase): ...@@ -473,7 +473,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase):
with cm(): with cm():
raise StopIteration("from with") raise StopIteration("from with")
self.assertRaises(StopIteration, shouldThrow) with self.assertRaisesRegex(StopIteration, 'from with'):
shouldThrow()
def testRaisedStopIteration3(self): def testRaisedStopIteration3(self):
# Another variant where the exception hasn't been instantiated # Another variant where the exception hasn't been instantiated
...@@ -486,8 +487,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase): ...@@ -486,8 +487,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase):
with cm(): with cm():
raise next(iter([])) raise next(iter([]))
with self.assertWarnsRegex(DeprecationWarning, "StopIteration"): with self.assertRaises(StopIteration):
self.assertRaises(StopIteration, shouldThrow) shouldThrow()
def testRaisedGeneratorExit1(self): def testRaisedGeneratorExit1(self):
# From bug 1462485 # From bug 1462485
......
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.
...@@ -248,59 +248,17 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) ...@@ -248,59 +248,17 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
Py_CLEAR(result); Py_CLEAR(result);
} }
else if (!result && PyErr_ExceptionMatches(PyExc_StopIteration)) { else if (!result && PyErr_ExceptionMatches(PyExc_StopIteration)) {
/* Check for __future__ generator_stop and conditionally turn const char *msg = "generator raised StopIteration";
* a leaking StopIteration into RuntimeError (with its cause if (PyCoro_CheckExact(gen)) {
* set appropriately). */ msg = "coroutine raised StopIteration";
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);
} }
else { else if PyAsyncGen_CheckExact(gen) {
/* `gen` is an ordinary generator without msg = "async generator raised StopIteration";
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);
}
} }
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
} }
else if (PyAsyncGen_CheckExact(gen) && !result && else if (!result && PyAsyncGen_CheckExact(gen) &&
PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) PyErr_ExceptionMatches(PyExc_StopAsyncIteration))
{ {
/* code in `gen` raised a StopAsyncIteration error: /* code in `gen` raised a StopAsyncIteration error:
......
...@@ -41,7 +41,7 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename) ...@@ -41,7 +41,7 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
} else if (strcmp(feature, FUTURE_BARRY_AS_BDFL) == 0) { } else if (strcmp(feature, FUTURE_BARRY_AS_BDFL) == 0) {
ff->ff_features |= CO_FUTURE_BARRY_AS_BDFL; ff->ff_features |= CO_FUTURE_BARRY_AS_BDFL;
} else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) { } else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) {
ff->ff_features |= CO_FUTURE_GENERATOR_STOP; continue;
} else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) { } else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) {
ff->ff_features |= CO_FUTURE_ANNOTATIONS; ff->ff_features |= CO_FUTURE_ANNOTATIONS;
} else if (strcmp(feature, "braces") == 0) { } else if (strcmp(feature, "braces") == 0) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment