Skip to content

Commit

Permalink
Issue 24237: Raise PendingDeprecationWarning per PEP 479
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
1st1 committed May 22, 2015
1 parent e79ec70 commit 6833339
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 29 deletions.
10 changes: 8 additions & 2 deletions Lib/difflib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,10 @@ def _line_pair_iterator():
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:
Expand All @@ -1609,7 +1612,10 @@ def _line_pair_iterator():
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
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ def test_contextmanager_except_stopiter(self):
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:
Expand Down
62 changes: 42 additions & 20 deletions Lib/test/test_generators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import gc
import sys
import unittest
import warnings
import weakref

from test import support
Expand Down Expand Up @@ -217,6 +218,46 @@ def gen():
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:
Expand Down Expand Up @@ -263,26 +304,7 @@ def gen():
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:
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_with.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,8 @@ def shouldThrow():
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
Expand All @@ -481,7 +482,8 @@ def shouldThrow():
with cm():
raise next(iter([]))

self.assertRaises(StopIteration, shouldThrow)
with self.assertWarnsRegex(PendingDeprecationWarning, "StopIteration"):
self.assertRaises(StopIteration, shouldThrow)

def testRaisedGeneratorExit1(self):
# From bug 1462485
Expand Down
23 changes: 20 additions & 3 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand Down

0 comments on commit 6833339

Please sign in to comment.