Skip to content

Commit 43c47fe

Browse files
authored
bpo-32670: Enforce PEP 479. (#5327)
1 parent dba976b commit 43c47fe

File tree

7 files changed

+38
-80
lines changed

7 files changed

+38
-80
lines changed

Doc/library/exceptions.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -367,17 +367,21 @@ The following exceptions are the exceptions that are usually raised.
367367
raised, and the value returned by the function is used as the
368368
:attr:`value` parameter to the constructor of the exception.
369369

370-
If a generator function defined in the presence of a ``from __future__
371-
import generator_stop`` directive raises :exc:`StopIteration`, it will be
372-
converted into a :exc:`RuntimeError` (retaining the :exc:`StopIteration`
373-
as the new exception's cause).
370+
If a generator code directly or indirectly raises :exc:`StopIteration`,
371+
it is converted into a :exc:`RuntimeError` (retaining the
372+
:exc:`StopIteration` as the new exception's cause).
374373

375374
.. versionchanged:: 3.3
376375
Added ``value`` attribute and the ability for generator functions to
377376
use it to return a value.
378377

379378
.. versionchanged:: 3.5
380-
Introduced the RuntimeError transformation.
379+
Introduced the RuntimeError transformation via
380+
``from __future__ import generator_stop``, see :pep:`479`.
381+
382+
.. versionchanged:: 3.7
383+
Enable :pep:`479` for all code by default: a :exc:`StopIteration`
384+
error raised in a generator is transformed into a :exc:`RuntimeError`.
381385

382386
.. exception:: StopAsyncIteration
383387

Doc/whatsnew/3.7.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,12 @@ that may require changes to your code.
935935
Changes in Python behavior
936936
--------------------------
937937

938+
* :pep:`479` is enabled for all code in Python 3.7, meaning that
939+
:exc:`StopIteration` exceptions raised directly or indirectly in
940+
coroutines and generators are transformed into :exc:`RuntimeError`
941+
exceptions.
942+
(Contributed by Yury Selivanov in :issue:`32670`.)
943+
938944
* Due to an oversight, earlier Python versions erroneously accepted the
939945
following syntax::
940946

Lib/test/test_generators.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -265,26 +265,16 @@ def gen():
265265
self.assertEqual(next(g), "done")
266266
self.assertEqual(sys.exc_info(), (None, None, None))
267267

268-
def test_stopiteration_warning(self):
268+
def test_stopiteration_error(self):
269269
# See also PEP 479.
270270

271271
def gen():
272272
raise StopIteration
273273
yield
274274

275-
with self.assertRaises(StopIteration), \
276-
self.assertWarnsRegex(DeprecationWarning, "StopIteration"):
277-
278-
next(gen())
279-
280-
with self.assertRaisesRegex(DeprecationWarning,
281-
"generator .* raised StopIteration"), \
282-
warnings.catch_warnings():
283-
284-
warnings.simplefilter('error')
275+
with self.assertRaisesRegex(RuntimeError, 'raised StopIteration'):
285276
next(gen())
286277

287-
288278
def test_tutorial_stopiteration(self):
289279
# Raise StopIteration" stops the generator too:
290280

@@ -296,13 +286,7 @@ def f():
296286
g = f()
297287
self.assertEqual(next(g), 1)
298288

299-
with self.assertWarnsRegex(DeprecationWarning, "StopIteration"):
300-
with self.assertRaises(StopIteration):
301-
next(g)
302-
303-
with self.assertRaises(StopIteration):
304-
# This time StopIteration isn't raised from the generator's body,
305-
# hence no warning.
289+
with self.assertRaisesRegex(RuntimeError, 'raised StopIteration'):
306290
next(g)
307291

308292
def test_return_tuple(self):

Lib/test/test_with.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,8 @@ def shouldThrow():
458458
with cm():
459459
raise StopIteration("from with")
460460

461-
with self.assertWarnsRegex(DeprecationWarning, "StopIteration"):
462-
self.assertRaises(StopIteration, shouldThrow)
461+
with self.assertRaisesRegex(StopIteration, 'from with'):
462+
shouldThrow()
463463

464464
def testRaisedStopIteration2(self):
465465
# From bug 1462485
@@ -473,7 +473,8 @@ def shouldThrow():
473473
with cm():
474474
raise StopIteration("from with")
475475

476-
self.assertRaises(StopIteration, shouldThrow)
476+
with self.assertRaisesRegex(StopIteration, 'from with'):
477+
shouldThrow()
477478

478479
def testRaisedStopIteration3(self):
479480
# Another variant where the exception hasn't been instantiated
@@ -486,8 +487,8 @@ def shouldThrow():
486487
with cm():
487488
raise next(iter([]))
488489

489-
with self.assertWarnsRegex(DeprecationWarning, "StopIteration"):
490-
self.assertRaises(StopIteration, shouldThrow)
490+
with self.assertRaises(StopIteration):
491+
shouldThrow()
491492

492493
def testRaisedGeneratorExit1(self):
493494
# From bug 1462485
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Enforce PEP 479 for all code.
2+
3+
This means that manually raising a StopIteration exception from a generator
4+
is prohibited for all code, regardless of whether 'from __future__ import
5+
generator_stop' was used or not.

Objects/genobject.c

Lines changed: 8 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -248,59 +248,17 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
248248
Py_CLEAR(result);
249249
}
250250
else if (!result && PyErr_ExceptionMatches(PyExc_StopIteration)) {
251-
/* Check for __future__ generator_stop and conditionally turn
252-
* a leaking StopIteration into RuntimeError (with its cause
253-
* set appropriately). */
254-
255-
const int check_stop_iter_error_flags = CO_FUTURE_GENERATOR_STOP |
256-
CO_COROUTINE |
257-
CO_ITERABLE_COROUTINE |
258-
CO_ASYNC_GENERATOR;
259-
260-
if (gen->gi_code != NULL &&
261-
((PyCodeObject *)gen->gi_code)->co_flags &
262-
check_stop_iter_error_flags)
263-
{
264-
/* `gen` is either:
265-
* a generator with CO_FUTURE_GENERATOR_STOP flag;
266-
* a coroutine;
267-
* a generator with CO_ITERABLE_COROUTINE flag
268-
(decorated with types.coroutine decorator);
269-
* an async generator.
270-
*/
271-
const char *msg = "generator raised StopIteration";
272-
if (PyCoro_CheckExact(gen)) {
273-
msg = "coroutine raised StopIteration";
274-
}
275-
else if PyAsyncGen_CheckExact(gen) {
276-
msg = "async generator raised StopIteration";
277-
}
278-
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
251+
const char *msg = "generator raised StopIteration";
252+
if (PyCoro_CheckExact(gen)) {
253+
msg = "coroutine raised StopIteration";
279254
}
280-
else {
281-
/* `gen` is an ordinary generator without
282-
CO_FUTURE_GENERATOR_STOP flag.
283-
*/
284-
285-
PyObject *exc, *val, *tb;
286-
287-
/* Pop the exception before issuing a warning. */
288-
PyErr_Fetch(&exc, &val, &tb);
289-
290-
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
291-
"generator '%.50S' raised StopIteration",
292-
gen->gi_qualname)) {
293-
/* Warning was converted to an error. */
294-
Py_XDECREF(exc);
295-
Py_XDECREF(val);
296-
Py_XDECREF(tb);
297-
}
298-
else {
299-
PyErr_Restore(exc, val, tb);
300-
}
255+
else if PyAsyncGen_CheckExact(gen) {
256+
msg = "async generator raised StopIteration";
301257
}
258+
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
259+
302260
}
303-
else if (PyAsyncGen_CheckExact(gen) && !result &&
261+
else if (!result && PyAsyncGen_CheckExact(gen) &&
304262
PyErr_ExceptionMatches(PyExc_StopAsyncIteration))
305263
{
306264
/* code in `gen` raised a StopAsyncIteration error:

Python/future.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
4141
} else if (strcmp(feature, FUTURE_BARRY_AS_BDFL) == 0) {
4242
ff->ff_features |= CO_FUTURE_BARRY_AS_BDFL;
4343
} else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) {
44-
ff->ff_features |= CO_FUTURE_GENERATOR_STOP;
44+
continue;
4545
} else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) {
4646
ff->ff_features |= CO_FUTURE_ANNOTATIONS;
4747
} else if (strcmp(feature, "braces") == 0) {

0 commit comments

Comments
 (0)