[Python-checkins] cpython (merge default -> default): Merge heads

serhiy.storchaka python-checkins at python.org
Sat Jan 31 11:24:05 CET 2015


https://hg.python.org/cpython/rev/2f8ad1424534
changeset:   94415:2f8ad1424534
parent:      94414:3603bae63c13
parent:      94413:cb5cdb0aa18d
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Sat Jan 31 12:23:01 2015 +0200
summary:
  Merge heads

files:
  Lib/test/test_generators.py |  109 ++++++++++++++++++++++++
  Misc/NEWS                   |    6 +
  Python/ceval.c              |    7 +-
  3 files changed, 119 insertions(+), 3 deletions(-)


diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -89,6 +89,115 @@
                          "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 = """
 Let's try a simple generator:
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -229,6 +229,12 @@
 - Issue #23326: Removed __ne__ implementations.  Since fixing default __ne__
   implementation in issue #21408 they are redundant.
 
+- 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
   non-seekable streams.
 
diff --git a/Python/ceval.c b/Python/ceval.c
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -1189,8 +1189,8 @@
     f->f_stacktop = NULL;       /* remains NULL unless yield suspends frame */
     f->f_executing = 1;
 
-    if (co->co_flags & CO_GENERATOR && !throwflag) {
-        if (f->f_exc_type != NULL && f->f_exc_type != Py_None) {
+    if (co->co_flags & CO_GENERATOR) {
+        if (!throwflag && f->f_exc_type != NULL && f->f_exc_type != Py_None) {
             /* We were in an except handler when we left,
                restore the exception state which was put aside
                (see YIELD_VALUE). */
@@ -3196,7 +3196,8 @@
             || (retval == NULL && PyErr_Occurred()));
 
 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
            state and restore that of the calling frame. If the current
            exception state is from the caller, we clear the exception values

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list