Author: Amaury Forgeot d'Arc <[email protected]>
Branch: py3.3
Changeset: r74202:a6a3f705a6df
Date: 2014-10-23 23:56 +0200
http://bitbucket.org/pypy/pypy/changeset/a6a3f705a6df/

Log:    Improve support for "yield from" generators.

diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -113,10 +113,58 @@
             w_val = self.space.w_None
         return self.throw(w_type, w_val, w_tb)
 
+    def _get_yield_from(self):
+        # Probably a hack (but CPython has the same):
+        # If the current frame is stopped in a "yield from",
+        # return the paused generator.
+        from pypy.interpreter.pyopcode import bytecode_spec
+        if not self.frame:
+            return None
+        co_code = self.frame.pycode.co_code
+        opcode = ord(co_code[self.frame.last_instr + 1])
+        if opcode == opcodedesc.YIELD_FROM.index:
+            return self.frame.peekvalue()
+
     def throw(self, w_type, w_val, w_tb):
         from pypy.interpreter.pytraceback import check_traceback
         space = self.space
 
+        w_yf = self._get_yield_from()
+        if w_yf is not None:
+            # Paused in a "yield from", pass the throw to the inner generator.
+            if space.is_w(w_type, space.w_GeneratorExit):
+                try:
+                    w_close = space.getattr(w_yf, space.wrap("close"))
+                except OperationError as e:
+                    if not e.match(space, space.w_AttributeError):
+                        e.write_unraisable(space, "generator.close()")
+                else:
+                    self.running = True
+                    try:
+                        space.call_function(w_close)
+                    except OperationError as operr:
+                        self.running = False
+                        self.send_ex(space.w_None, operr)
+                        return
+                    finally:
+                        self.running = False
+                return self._throw_here(space, w_type, w_val, w_tb)
+            else:
+                try:
+                    space.call_method(w_yf, "throw", w_type, w_val, w_tb)
+                except OperationError as operr:
+                    self.running = False
+                    self.send_ex(space.w_None, operr)
+                    return
+                finally:
+                    self.running = False
+                
+            return self.forward_throw_to_yield_from(yf)
+
+        # Not paused in a "yield from", quit this generator
+        return self._throw_here(space, w_type, w_val, w_tb)
+
+    def _throw_here(self, space, w_type, w_val, w_tb):
         msg = "throw() third argument must be a traceback object"
         if space.is_none(w_tb):
             tb = None
diff --git a/pypy/interpreter/test/test_generator.py 
b/pypy/interpreter/test/test_generator.py
--- a/pypy/interpreter/test/test_generator.py
+++ b/pypy/interpreter/test/test_generator.py
@@ -364,3 +364,158 @@
         return g.__code__
     ''')
     assert should_not_inline(w_co) == True
+
+class AppTestYieldFrom:
+    def test_delegating_close(self):
+        """
+        Test delegating 'close'
+        """
+        trace = []
+        d = dict(trace=trace)
+        exec('''if 1:
+        def g1():
+            try:
+                trace.append("Starting g1")
+                yield "g1 ham"
+                yield from g2()
+                yield "g1 eggs"
+            finally:
+                trace.append("Finishing g1")
+        def g2():
+            try:
+                trace.append("Starting g2")
+                yield "g2 spam"
+                yield "g2 more spam"
+            finally:
+                trace.append("Finishing g2")
+        ''', d)
+        g1, g2 = d['g1'], d['g2']
+        g = g1()
+        for i in range(2):
+            x = next(g)
+            trace.append("Yielded %s" % (x,))
+        g.close()
+        assert trace == [
+            "Starting g1",
+            "Yielded g1 ham",
+            "Starting g2",
+            "Yielded g2 spam",
+            "Finishing g2",
+            "Finishing g1"
+        ]
+
+    def test_handing_exception_while_delegating_close(self):
+        """
+        Test handling exception while delegating 'close'
+        """
+        trace = []
+        d = dict(trace=trace)
+        exec('''if 1:
+        def g1():
+            try:
+                trace.append("Starting g1")
+                yield "g1 ham"
+                yield from g2()
+                yield "g1 eggs"
+            finally:
+                trace.append("Finishing g1")
+        def g2():
+            try:
+                trace.append("Starting g2")
+                yield "g2 spam"
+                yield "g2 more spam"
+            finally:
+                trace.append("Finishing g2")
+                raise ValueError("nybbles have exploded with delight")
+        ''', d)
+        g1, g2 = d['g1'], d['g2']
+        g = g1()
+        for i in range(2):
+            x = next(g)
+            trace.append("Yielded %s" % (x,))
+        exc = raises(ValueError, g.close)
+        assert exc.value.args[0] == "nybbles have exploded with delight"
+        assert isinstance(exc.value.__context__, GeneratorExit)
+        assert trace == [
+            "Starting g1",
+            "Yielded g1 ham",
+            "Starting g2",
+            "Yielded g2 spam",
+            "Finishing g2",
+            "Finishing g1",
+        ]
+
+    def test_delegating_throw(self):
+        """
+        Test delegating 'throw'
+        """
+        trace = []
+        d = dict(trace=trace)
+        exec('''if 1:
+        def g1():
+            try:
+                trace.append("Starting g1")
+                yield "g1 ham"
+                yield from g2()
+                yield "g1 eggs"
+            finally:
+                trace.append("Finishing g1")
+        def g2():
+            try:
+                trace.append("Starting g2")
+                yield "g2 spam"
+                yield "g2 more spam"
+            finally:
+                trace.append("Finishing g2")
+        ''', d)
+        g1, g2 = d['g1'], d['g2']
+        g = g1()
+        for i in range(2):
+            x = next(g)
+            trace.append("Yielded %s" % (x,))
+        e = ValueError("tomato ejected")
+        exc = raises(ValueError, g.throw, e)
+        assert exc.value.args[0] == "tomato ejected"
+        assert trace == [
+            "Starting g1",
+            "Yielded g1 ham",
+            "Starting g2",
+            "Yielded g2 spam",
+            "Finishing g2",
+            "Finishing g1",
+        ]
+
+    def test_broken_getattr_handling(self):
+        """
+        Test subiterator with a broken getattr implementation
+        """
+        class Broken:
+            def __iter__(self):
+                return self
+            def __next__(self):
+                return 1
+            def __getattr__(self, attr):
+                1/0
+
+        d = dict(Broken=Broken)
+        exec('''if 1:
+        def g():
+            yield from Broken()
+        ''', d)
+        g = d['g']
+
+        gi = g()
+        assert next(gi) == 1
+        raises(ZeroDivisionError, gi.send, 1)
+
+        gi = g()
+        assert next(gi) == 1
+        raises(ZeroDivisionError, gi.throw, RuntimeError)
+
+        gi = g()
+        assert next(gi) == 1
+        import io, sys
+        sys.stderr = io.StringIO()
+        gi.close()
+        assert 'ZeroDivisionError' in sys.stderr.getvalue()
+    
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to