bpo-32670: Enforce PEP 479. (#5327)
This commit is contained in:
parent
dba976b8a2
commit
43c47fe096
@ -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.
|
|||||||
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):
|
|||||||
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())
|
next(gen())
|
||||||
|
|
||||||
with self.assertRaisesRegex(DeprecationWarning,
|
|
||||||
"generator .* raised StopIteration"), \
|
|
||||||
warnings.catch_warnings():
|
|
||||||
|
|
||||||
warnings.simplefilter('error')
|
|
||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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
|
||||||
|
@ -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.
|
@ -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)
|
|||||||
} 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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user