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