Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6833339

Browse files
committed
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.
1 parent e79ec70 commit 6833339

5 files changed

Lines changed: 78 additions & 29 deletions

File tree

Lib/difflib.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,7 +1582,10 @@ def _line_pair_iterator():
15821582
while True:
15831583
# Collecting lines of text until we have a from/to pair
15841584
while (len(fromlines)==0 or len(tolines)==0):
1585-
from_line, to_line, found_diff = next(line_iterator)
1585+
try:
1586+
from_line, to_line, found_diff = next(line_iterator)
1587+
except StopIteration:
1588+
return
15861589
if from_line is not None:
15871590
fromlines.append((from_line,found_diff))
15881591
if to_line is not None:
@@ -1609,7 +1612,10 @@ def _line_pair_iterator():
16091612
index, contextLines = 0, [None]*(context)
16101613
found_diff = False
16111614
while(found_diff is False):
1612-
from_line, to_line, found_diff = next(line_pair_iterator)
1615+
try:
1616+
from_line, to_line, found_diff = next(line_pair_iterator)
1617+
except StopIteration:
1618+
return
16131619
i = index % context
16141620
contextLines[i] = (from_line, to_line, found_diff)
16151621
index += 1

Lib/test/test_contextlib.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,10 @@ def test_contextmanager_except_stopiter(self):
8989
def woohoo():
9090
yield
9191
try:
92-
with woohoo():
93-
raise stop_exc
92+
with self.assertWarnsRegex(PendingDeprecationWarning,
93+
"StopIteration"):
94+
with woohoo():
95+
raise stop_exc
9496
except Exception as ex:
9597
self.assertIs(ex, stop_exc)
9698
else:

Lib/test/test_generators.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import gc
22
import sys
33
import unittest
4+
import warnings
45
import weakref
56

67
from test import support
@@ -217,6 +218,46 @@ def gen():
217218
self.assertEqual(next(g), "done")
218219
self.assertEqual(sys.exc_info(), (None, None, None))
219220

221+
def test_stopiteration_warning(self):
222+
# See also PEP 479.
223+
224+
def gen():
225+
raise StopIteration
226+
yield
227+
228+
with self.assertRaises(StopIteration), \
229+
self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
230+
231+
next(gen())
232+
233+
with self.assertRaisesRegex(PendingDeprecationWarning,
234+
"generator .* raised StopIteration"), \
235+
warnings.catch_warnings():
236+
237+
warnings.simplefilter('error')
238+
next(gen())
239+
240+
241+
def test_tutorial_stopiteration(self):
242+
# Raise StopIteration" stops the generator too:
243+
244+
def f():
245+
yield 1
246+
raise StopIteration
247+
yield 2 # never reached
248+
249+
g = f()
250+
self.assertEqual(next(g), 1)
251+
252+
with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
253+
with self.assertRaises(StopIteration):
254+
next(g)
255+
256+
with self.assertRaises(StopIteration):
257+
# This time StopIteration isn't raised from the generator's body,
258+
# hence no warning.
259+
next(g)
260+
220261

221262
tutorial_tests = """
222263
Let's try a simple generator:
@@ -263,26 +304,7 @@ def gen():
263304
File "<stdin>", line 1, in ?
264305
StopIteration
265306
266-
"raise StopIteration" stops the generator too:
267-
268-
>>> def f():
269-
... yield 1
270-
... raise StopIteration
271-
... yield 2 # never reached
272-
...
273-
>>> g = f()
274-
>>> next(g)
275-
1
276-
>>> next(g)
277-
Traceback (most recent call last):
278-
File "<stdin>", line 1, in ?
279-
StopIteration
280-
>>> next(g)
281-
Traceback (most recent call last):
282-
File "<stdin>", line 1, in ?
283-
StopIteration
284-
285-
However, they are not exactly equivalent:
307+
However, "return" and StopIteration are not exactly equivalent:
286308
287309
>>> def g1():
288310
... try:

Lib/test/test_with.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,8 @@ def shouldThrow():
454454
with cm():
455455
raise StopIteration("from with")
456456

457-
self.assertRaises(StopIteration, shouldThrow)
457+
with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
458+
self.assertRaises(StopIteration, shouldThrow)
458459

459460
def testRaisedStopIteration2(self):
460461
# From bug 1462485
@@ -481,7 +482,8 @@ def shouldThrow():
481482
with cm():
482483
raise next(iter([]))
483484

484-
self.assertRaises(StopIteration, shouldThrow)
485+
with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
486+
self.assertRaises(StopIteration, shouldThrow)
485487

486488
def testRaisedGeneratorExit1(self):
487489
# From bug 1462485

Objects/genobject.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,12 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
143143
}
144144
Py_CLEAR(result);
145145
}
146-
else if (!result) {
146+
else if (!result && PyErr_ExceptionMatches(PyExc_StopIteration)) {
147147
/* Check for __future__ generator_stop and conditionally turn
148148
* a leaking StopIteration into RuntimeError (with its cause
149149
* set appropriately). */
150-
if ((((PyCodeObject *)gen->gi_code)->co_flags &
150+
if (((PyCodeObject *)gen->gi_code)->co_flags &
151151
(CO_FUTURE_GENERATOR_STOP | CO_COROUTINE | CO_ITERABLE_COROUTINE))
152-
&& PyErr_ExceptionMatches(PyExc_StopIteration))
153152
{
154153
PyObject *exc, *val, *val2, *tb;
155154
PyErr_Fetch(&exc, &val, &tb);
@@ -167,6 +166,24 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
167166
PyException_SetContext(val2, val);
168167
PyErr_Restore(exc, val2, tb);
169168
}
169+
else {
170+
PyObject *exc, *val, *tb;
171+
172+
/* Pop the exception before issuing a warning. */
173+
PyErr_Fetch(&exc, &val, &tb);
174+
175+
if (PyErr_WarnFormat(PyExc_PendingDeprecationWarning, 1,
176+
"generator '%.50S' raised StopIteration",
177+
gen->gi_qualname)) {
178+
/* Warning was converted to an error. */
179+
Py_XDECREF(exc);
180+
Py_XDECREF(val);
181+
Py_XDECREF(tb);
182+
}
183+
else {
184+
PyErr_Restore(exc, val, tb);
185+
}
186+
}
170187
}
171188

172189
if (!result || f->f_stacktop == NULL) {

0 commit comments

Comments
 (0)