Issue 24237: Raise PendingDeprecationWarning per PEP 479
Raise PendingDeprecationWarning when generator raises StopIteration and no __future__ import is used. Fix offenders in the stdlib and tests. See also issue 22906. Thanks to Nick Coghlan and Berker Peksag for reviews.
This commit is contained in:
parent
e79ec70801
commit
683333955a
|
@ -1582,7 +1582,10 @@ def _mdiff(fromlines, tolines, context=None, linejunk=None,
|
|||
while True:
|
||||
# Collecting lines of text until we have a from/to pair
|
||||
while (len(fromlines)==0 or len(tolines)==0):
|
||||
from_line, to_line, found_diff = next(line_iterator)
|
||||
try:
|
||||
from_line, to_line, found_diff = next(line_iterator)
|
||||
except StopIteration:
|
||||
return
|
||||
if from_line is not None:
|
||||
fromlines.append((from_line,found_diff))
|
||||
if to_line is not None:
|
||||
|
@ -1609,7 +1612,10 @@ def _mdiff(fromlines, tolines, context=None, linejunk=None,
|
|||
index, contextLines = 0, [None]*(context)
|
||||
found_diff = False
|
||||
while(found_diff is False):
|
||||
from_line, to_line, found_diff = next(line_pair_iterator)
|
||||
try:
|
||||
from_line, to_line, found_diff = next(line_pair_iterator)
|
||||
except StopIteration:
|
||||
return
|
||||
i = index % context
|
||||
contextLines[i] = (from_line, to_line, found_diff)
|
||||
index += 1
|
||||
|
|
|
@ -89,8 +89,10 @@ class ContextManagerTestCase(unittest.TestCase):
|
|||
def woohoo():
|
||||
yield
|
||||
try:
|
||||
with woohoo():
|
||||
raise stop_exc
|
||||
with self.assertWarnsRegex(PendingDeprecationWarning,
|
||||
"StopIteration"):
|
||||
with woohoo():
|
||||
raise stop_exc
|
||||
except Exception as ex:
|
||||
self.assertIs(ex, stop_exc)
|
||||
else:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import gc
|
||||
import sys
|
||||
import unittest
|
||||
import warnings
|
||||
import weakref
|
||||
|
||||
from test import support
|
||||
|
@ -217,6 +218,46 @@ class ExceptionTest(unittest.TestCase):
|
|||
self.assertEqual(next(g), "done")
|
||||
self.assertEqual(sys.exc_info(), (None, None, None))
|
||||
|
||||
def test_stopiteration_warning(self):
|
||||
# See also PEP 479.
|
||||
|
||||
def gen():
|
||||
raise StopIteration
|
||||
yield
|
||||
|
||||
with self.assertRaises(StopIteration), \
|
||||
self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
|
||||
|
||||
next(gen())
|
||||
|
||||
with self.assertRaisesRegex(PendingDeprecationWarning,
|
||||
"generator .* raised StopIteration"), \
|
||||
warnings.catch_warnings():
|
||||
|
||||
warnings.simplefilter('error')
|
||||
next(gen())
|
||||
|
||||
|
||||
def test_tutorial_stopiteration(self):
|
||||
# Raise StopIteration" stops the generator too:
|
||||
|
||||
def f():
|
||||
yield 1
|
||||
raise StopIteration
|
||||
yield 2 # never reached
|
||||
|
||||
g = f()
|
||||
self.assertEqual(next(g), 1)
|
||||
|
||||
with self.assertWarnsRegex(PendingDeprecationWarning, "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)
|
||||
|
||||
|
||||
tutorial_tests = """
|
||||
Let's try a simple generator:
|
||||
|
@ -263,26 +304,7 @@ Let's try a simple generator:
|
|||
File "<stdin>", line 1, in ?
|
||||
StopIteration
|
||||
|
||||
"raise StopIteration" stops the generator too:
|
||||
|
||||
>>> def f():
|
||||
... yield 1
|
||||
... raise StopIteration
|
||||
... yield 2 # never reached
|
||||
...
|
||||
>>> g = f()
|
||||
>>> next(g)
|
||||
1
|
||||
>>> next(g)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
StopIteration
|
||||
>>> next(g)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
StopIteration
|
||||
|
||||
However, they are not exactly equivalent:
|
||||
However, "return" and StopIteration are not exactly equivalent:
|
||||
|
||||
>>> def g1():
|
||||
... try:
|
||||
|
|
|
@ -454,7 +454,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase):
|
|||
with cm():
|
||||
raise StopIteration("from with")
|
||||
|
||||
self.assertRaises(StopIteration, shouldThrow)
|
||||
with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
|
||||
self.assertRaises(StopIteration, shouldThrow)
|
||||
|
||||
def testRaisedStopIteration2(self):
|
||||
# From bug 1462485
|
||||
|
@ -481,7 +482,8 @@ class ExceptionalTestCase(ContextmanagerAssertionMixin, unittest.TestCase):
|
|||
with cm():
|
||||
raise next(iter([]))
|
||||
|
||||
self.assertRaises(StopIteration, shouldThrow)
|
||||
with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
|
||||
self.assertRaises(StopIteration, shouldThrow)
|
||||
|
||||
def testRaisedGeneratorExit1(self):
|
||||
# From bug 1462485
|
||||
|
|
|
@ -143,13 +143,12 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
|
|||
}
|
||||
Py_CLEAR(result);
|
||||
}
|
||||
else if (!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). */
|
||||
if ((((PyCodeObject *)gen->gi_code)->co_flags &
|
||||
if (((PyCodeObject *)gen->gi_code)->co_flags &
|
||||
(CO_FUTURE_GENERATOR_STOP | CO_COROUTINE | CO_ITERABLE_COROUTINE))
|
||||
&& PyErr_ExceptionMatches(PyExc_StopIteration))
|
||||
{
|
||||
PyObject *exc, *val, *val2, *tb;
|
||||
PyErr_Fetch(&exc, &val, &tb);
|
||||
|
@ -167,6 +166,24 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
|
|||
PyException_SetContext(val2, val);
|
||||
PyErr_Restore(exc, val2, tb);
|
||||
}
|
||||
else {
|
||||
PyObject *exc, *val, *tb;
|
||||
|
||||
/* Pop the exception before issuing a warning. */
|
||||
PyErr_Fetch(&exc, &val, &tb);
|
||||
|
||||
if (PyErr_WarnFormat(PyExc_PendingDeprecationWarning, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result || f->f_stacktop == NULL) {
|
||||
|
|
Loading…
Reference in New Issue