]> granicus.if.org Git - python/commitdiff
Merge 3.4 (generator)
authorVictor Stinner <victor.stinner@gmail.com>
Sat, 31 Jan 2015 10:08:40 +0000 (11:08 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Sat, 31 Jan 2015 10:08:40 +0000 (11:08 +0100)
1  2 
Lib/test/test_generators.py
Misc/NEWS
Python/ceval.c

index 3882f4cb32231bac520cfb2ba0be78256adb3f5c,7825a77339054faf81f9f91a52ac6384995cae7e..9e610132cd7cefe1a5942d03d602a79431913ecf
@@@ -50,45 -50,115 +50,154 @@@ class FinalizationTest(unittest.TestCas
          self.assertEqual(gc.garbage, old_garbage)
  
  
 +class GeneratorTest(unittest.TestCase):
 +
 +    def test_name(self):
 +        def func():
 +            yield 1
 +
 +        # check generator names
 +        gen = func()
 +        self.assertEqual(gen.__name__, "func")
 +        self.assertEqual(gen.__qualname__,
 +                         "GeneratorTest.test_name.<locals>.func")
 +
 +        # modify generator names
 +        gen.__name__ = "name"
 +        gen.__qualname__ = "qualname"
 +        self.assertEqual(gen.__name__, "name")
 +        self.assertEqual(gen.__qualname__, "qualname")
 +
 +        # generator names must be a string and cannot be deleted
 +        self.assertRaises(TypeError, setattr, gen, '__name__', 123)
 +        self.assertRaises(TypeError, setattr, gen, '__qualname__', 123)
 +        self.assertRaises(TypeError, delattr, gen, '__name__')
 +        self.assertRaises(TypeError, delattr, gen, '__qualname__')
 +
 +        # modify names of the function creating the generator
 +        func.__qualname__ = "func_qualname"
 +        func.__name__ = "func_name"
 +        gen = func()
 +        self.assertEqual(gen.__name__, "func_name")
 +        self.assertEqual(gen.__qualname__, "func_qualname")
 +
 +        # unnamed generator
 +        gen = (x for x in range(10))
 +        self.assertEqual(gen.__name__,
 +                         "<genexpr>")
 +        self.assertEqual(gen.__qualname__,
 +                         "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 --cc Misc/NEWS
index 01af9d6f6f0de9ac81d0a1810aa8d220cd957de8,f0518645b7e8aceafd3ebaf813e9a3b9c7150c71..495fd37e4465885d5a7bc4d7ee7e97ea0793c70a
+++ b/Misc/NEWS
@@@ -226,14 -50,12 +226,20 @@@ Core and Builtin
  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
 +  non-seekable streams.
 +
 +- Issue #14099: Writing to ZipFile and reading multiple ZipExtFiles is
 +  threadsafe now.
 +
 +- Issue #19361: JSON decoder now raises JSONDecodeError instead of ValueError.
 +
  - Issue #18518: timeit now rejects statements which can't be compiled outside
    a function or a loop (e.g. "return" or "break").
  
diff --cc Python/ceval.c
Simple merge