Merge 3.4 (generator)

This commit is contained in:
Victor Stinner 2015-01-31 11:08:40 +01:00
commit 13a1c6022b
3 changed files with 119 additions and 3 deletions

View File

@ -89,6 +89,115 @@ class GeneratorTest(unittest.TestCase):
"GeneratorTest.test_name.<locals>.<genexpr>") "GeneratorTest.test_name.<locals>.<genexpr>")
class ExceptionTest(unittest.TestCase):
# Tests for the issue #23353: check that the currently handled exception
# is correctly saved/restored in PyEval_EvalFrameEx().
def test_except_throw(self):
def store_raise_exc_generator():
try:
self.assertEqual(sys.exc_info()[0], None)
yield
except Exception as exc:
# exception raised by gen.throw(exc)
self.assertEqual(sys.exc_info()[0], ValueError)
self.assertIsNone(exc.__context__)
yield
# ensure that the exception is not lost
self.assertEqual(sys.exc_info()[0], ValueError)
yield
# we should be able to raise back the ValueError
raise
make = store_raise_exc_generator()
next(make)
try:
raise ValueError()
except Exception as exc:
try:
make.throw(exc)
except Exception:
pass
next(make)
with self.assertRaises(ValueError) as cm:
next(make)
self.assertIsNone(cm.exception.__context__)
self.assertEqual(sys.exc_info(), (None, None, None))
def test_except_next(self):
def gen():
self.assertEqual(sys.exc_info()[0], ValueError)
yield "done"
g = gen()
try:
raise ValueError
except Exception:
self.assertEqual(next(g), "done")
self.assertEqual(sys.exc_info(), (None, None, None))
def test_except_gen_except(self):
def gen():
try:
self.assertEqual(sys.exc_info()[0], None)
yield
# we are called from "except ValueError:", TypeError must
# inherit ValueError in its context
raise TypeError()
except TypeError as exc:
self.assertEqual(sys.exc_info()[0], TypeError)
self.assertEqual(type(exc.__context__), ValueError)
# here we are still called from the "except ValueError:"
self.assertEqual(sys.exc_info()[0], ValueError)
yield
self.assertIsNone(sys.exc_info()[0])
yield "done"
g = gen()
next(g)
try:
raise ValueError
except Exception:
next(g)
self.assertEqual(next(g), "done")
self.assertEqual(sys.exc_info(), (None, None, None))
def test_except_throw_exception_context(self):
def gen():
try:
try:
self.assertEqual(sys.exc_info()[0], None)
yield
except ValueError:
# we are called from "except ValueError:"
self.assertEqual(sys.exc_info()[0], ValueError)
raise TypeError()
except Exception as exc:
self.assertEqual(sys.exc_info()[0], TypeError)
self.assertEqual(type(exc.__context__), ValueError)
# we are still called from "except ValueError:"
self.assertEqual(sys.exc_info()[0], ValueError)
yield
self.assertIsNone(sys.exc_info()[0])
yield "done"
g = gen()
next(g)
try:
raise ValueError
except Exception as exc:
g.throw(exc)
self.assertEqual(next(g), "done")
self.assertEqual(sys.exc_info(), (None, None, None))
tutorial_tests = """ tutorial_tests = """
Let's try a simple generator: Let's try a simple generator:

View File

@ -226,6 +226,12 @@ Core and Builtins
Library Library
------- -------
- Issue #23353: Fix the exception handling of generators in
PyEval_EvalFrameEx(). At entry, save or swap the exception state even if
PyEval_EvalFrameEx() is called with throwflag=0. At exit, the exception state
is now always restored or swapped, not only if why is WHY_YIELD or
WHY_RETURN. Patch co-written with Antoine Pitrou.
- Issue #14099: Restored support of writing ZIP files to tellable but - Issue #14099: Restored support of writing ZIP files to tellable but
non-seekable streams. non-seekable streams.

View File

@ -1189,8 +1189,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */ f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */
f->f_executing = 1; f->f_executing = 1;
if (co->co_flags & CO_GENERATOR && !throwflag) { if (co->co_flags & CO_GENERATOR) {
if (f->f_exc_type != NULL && f->f_exc_type != Py_None) { if (!throwflag && f->f_exc_type != NULL && f->f_exc_type != Py_None) {
/* We were in an except handler when we left, /* We were in an except handler when we left,
restore the exception state which was put aside restore the exception state which was put aside
(see YIELD_VALUE). */ (see YIELD_VALUE). */
@ -3196,7 +3196,8 @@ fast_block_end:
|| (retval == NULL && PyErr_Occurred())); || (retval == NULL && PyErr_Occurred()));
fast_yield: fast_yield:
if (co->co_flags & CO_GENERATOR && (why == WHY_YIELD || why == WHY_RETURN)) { if (co->co_flags & CO_GENERATOR) {
/* The purpose of this block is to put aside the generator's exception /* The purpose of this block is to put aside the generator's exception
state and restore that of the calling frame. If the current state and restore that of the calling frame. If the current
exception state is from the caller, we clear the exception values exception state is from the caller, we clear the exception values