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

Skip to content

Commit 13a1c60

Browse files
committed
Merge 3.4 (generator)
2 parents 57f7db3 + 26f7b8a commit 13a1c60

3 files changed

Lines changed: 119 additions & 3 deletions

File tree

Lib/test/test_generators.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,115 @@ def func():
8989
"GeneratorTest.test_name.<locals>.<genexpr>")
9090

9191

92+
class ExceptionTest(unittest.TestCase):
93+
# Tests for the issue #23353: check that the currently handled exception
94+
# is correctly saved/restored in PyEval_EvalFrameEx().
95+
96+
def test_except_throw(self):
97+
def store_raise_exc_generator():
98+
try:
99+
self.assertEqual(sys.exc_info()[0], None)
100+
yield
101+
except Exception as exc:
102+
# exception raised by gen.throw(exc)
103+
self.assertEqual(sys.exc_info()[0], ValueError)
104+
self.assertIsNone(exc.__context__)
105+
yield
106+
107+
# ensure that the exception is not lost
108+
self.assertEqual(sys.exc_info()[0], ValueError)
109+
yield
110+
111+
# we should be able to raise back the ValueError
112+
raise
113+
114+
make = store_raise_exc_generator()
115+
next(make)
116+
117+
try:
118+
raise ValueError()
119+
except Exception as exc:
120+
try:
121+
make.throw(exc)
122+
except Exception:
123+
pass
124+
125+
next(make)
126+
with self.assertRaises(ValueError) as cm:
127+
next(make)
128+
self.assertIsNone(cm.exception.__context__)
129+
130+
self.assertEqual(sys.exc_info(), (None, None, None))
131+
132+
def test_except_next(self):
133+
def gen():
134+
self.assertEqual(sys.exc_info()[0], ValueError)
135+
yield "done"
136+
137+
g = gen()
138+
try:
139+
raise ValueError
140+
except Exception:
141+
self.assertEqual(next(g), "done")
142+
self.assertEqual(sys.exc_info(), (None, None, None))
143+
144+
def test_except_gen_except(self):
145+
def gen():
146+
try:
147+
self.assertEqual(sys.exc_info()[0], None)
148+
yield
149+
# we are called from "except ValueError:", TypeError must
150+
# inherit ValueError in its context
151+
raise TypeError()
152+
except TypeError as exc:
153+
self.assertEqual(sys.exc_info()[0], TypeError)
154+
self.assertEqual(type(exc.__context__), ValueError)
155+
# here we are still called from the "except ValueError:"
156+
self.assertEqual(sys.exc_info()[0], ValueError)
157+
yield
158+
self.assertIsNone(sys.exc_info()[0])
159+
yield "done"
160+
161+
g = gen()
162+
next(g)
163+
try:
164+
raise ValueError
165+
except Exception:
166+
next(g)
167+
168+
self.assertEqual(next(g), "done")
169+
self.assertEqual(sys.exc_info(), (None, None, None))
170+
171+
def test_except_throw_exception_context(self):
172+
def gen():
173+
try:
174+
try:
175+
self.assertEqual(sys.exc_info()[0], None)
176+
yield
177+
except ValueError:
178+
# we are called from "except ValueError:"
179+
self.assertEqual(sys.exc_info()[0], ValueError)
180+
raise TypeError()
181+
except Exception as exc:
182+
self.assertEqual(sys.exc_info()[0], TypeError)
183+
self.assertEqual(type(exc.__context__), ValueError)
184+
# we are still called from "except ValueError:"
185+
self.assertEqual(sys.exc_info()[0], ValueError)
186+
yield
187+
self.assertIsNone(sys.exc_info()[0])
188+
yield "done"
189+
190+
g = gen()
191+
next(g)
192+
try:
193+
raise ValueError
194+
except Exception as exc:
195+
g.throw(exc)
196+
197+
self.assertEqual(next(g), "done")
198+
self.assertEqual(sys.exc_info(), (None, None, None))
199+
200+
92201
tutorial_tests = """
93202
Let's try a simple generator:
94203

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ Core and Builtins
226226
Library
227227
-------
228228

229+
- Issue #23353: Fix the exception handling of generators in
230+
PyEval_EvalFrameEx(). At entry, save or swap the exception state even if
231+
PyEval_EvalFrameEx() is called with throwflag=0. At exit, the exception state
232+
is now always restored or swapped, not only if why is WHY_YIELD or
233+
WHY_RETURN. Patch co-written with Antoine Pitrou.
234+
229235
- Issue #14099: Restored support of writing ZIP files to tellable but
230236
non-seekable streams.
231237

Python/ceval.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,8 +1189,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
11891189
f->f_stacktop = NULL; /* remains NULL unless yield suspends frame */
11901190
f->f_executing = 1;
11911191

1192-
if (co->co_flags & CO_GENERATOR && !throwflag) {
1193-
if (f->f_exc_type != NULL && f->f_exc_type != Py_None) {
1192+
if (co->co_flags & CO_GENERATOR) {
1193+
if (!throwflag && f->f_exc_type != NULL && f->f_exc_type != Py_None) {
11941194
/* We were in an except handler when we left,
11951195
restore the exception state which was put aside
11961196
(see YIELD_VALUE). */
@@ -3196,7 +3196,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
31963196
|| (retval == NULL && PyErr_Occurred()));
31973197

31983198
fast_yield:
3199-
if (co->co_flags & CO_GENERATOR && (why == WHY_YIELD || why == WHY_RETURN)) {
3199+
if (co->co_flags & CO_GENERATOR) {
3200+
32003201
/* The purpose of this block is to put aside the generator's exception
32013202
state and restore that of the calling frame. If the current
32023203
exception state is from the caller, we clear the exception values

0 commit comments

Comments
 (0)