summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYury Selivanov <yselivanov@sprymix.com>2015-05-22 11:16:47 -0400
committerYury Selivanov <yselivanov@sprymix.com>2015-05-22 11:16:47 -0400
commit6a0d5881d488263bdeb460d70a87b8e10b282c02 (patch)
tree4f9d87b97991091ca69ef6418fac9bd684fcfe4b
parent647cf1e9cabefeae8524838ab0bf2f9cc3ce16cc (diff)
downloadcpython-6a0d5881d488263bdeb460d70a87b8e10b282c02.tar.gz
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.
-rw-r--r--Lib/difflib.py10
-rw-r--r--Lib/test/test_contextlib.py6
-rw-r--r--Lib/test/test_generators.py62
-rw-r--r--Lib/test/test_with.py6
-rw-r--r--Objects/genobject.c23
5 files changed, 78 insertions, 29 deletions
diff --git a/Lib/difflib.py b/Lib/difflib.py
index aa98436ce3..22d9145a07 100644
--- a/Lib/difflib.py
+++ b/Lib/difflib.py
@@ -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
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
index a5d68a99f8..adafc9fd4f 100644
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -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:
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index 85e09a1f57..fe4b138c37 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -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:
diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py
index fcd28f6735..5dbd1e6325 100644
--- a/Lib/test/test_with.py
+++ b/Lib/test/test_with.py
@@ -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
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 555e4fd76e..8e5624d460 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -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) {