Author: Armin Rigo <[email protected]>
Branch: py3.5-corowrapper
Changeset: r87143:f3788fad20bf
Date: 2016-09-16 17:48 +0200
http://bitbucket.org/pypy/pypy/changeset/f3788fad20bf/
Log: 'yield from' starts to work again, a bit
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -1,6 +1,6 @@
from pypy.interpreter.baseobjspace import W_Root
from pypy.interpreter.error import OperationError, oefmt
-from pypy.interpreter.pyopcode import LoopBlock, SApplicationException
+from pypy.interpreter.pyopcode import LoopBlock, SApplicationException, Yield
from pypy.interpreter.pycode import CO_YIELD_INSIDE_TRY
from pypy.interpreter.astcompiler import consts
from rpython.rlib import jit
@@ -151,9 +151,10 @@
# interpretation.
space = self.space
if self.w_yielded_from is not None:
- XXX
-
- if isinstance(w_arg_or_err, SApplicationException):
+ w_arg_or_err = self.next_yield_from(frame, w_arg_or_err)
+ # Normal case: the call above raises Yield.
+ # We reach this point if the iterable is exhausted.
+ elif isinstance(w_arg_or_err, SApplicationException):
ec = space.getexecutioncontext()
return frame.handle_operation_error(ec, w_arg_or_err.operr)
@@ -167,6 +168,40 @@
frame.pushvalue(w_arg_or_err)
return last_instr + 1
+ def next_yield_from(self, frame, w_inputvalue_or_err):
+ """Fetch the next item of the current 'yield from', push it on
+ the frame stack, and raises Yield. If there isn't one, push
+ w_stopiteration_value and returns. May also just raise.
+ """
+ space = self.space
+ w_yf = self.w_yielded_from
+ try:
+ if isinstance(w_yf, GeneratorOrCoroutine):
+ w_retval = w_yf.send_ex(w_inputvalue_or_err)
+ elif space.is_w(w_inputvalue_or_err, space.w_None):
+ w_retval = space.next(w_yf)
+ elif isinstance(w_inputvalue_or_err, SApplicationException):
+ raise w_inputvalue_or_err.operr
+ else:
+ w_retval = space.call_method(w_gen, "send",
w_inputvalue_or_err)
+ except OperationError as e:
+ self.w_yielded_from = None
+ if not e.match(space, space.w_StopIteration):
+ raise
+ e.normalize_exception(space)
+ try:
+ w_stop_value = space.getattr(e.get_w_value(space),
+ space.wrap("value"))
+ except OperationError as e:
+ if not e.match(space, space.w_AttributeError):
+ raise
+ w_value = space.w_None
+ frame.pushvalue(w_stop_value)
+ return
+ else:
+ frame.pushvalue(w_retval)
+ raise Yield
+
def _leak_stopiteration(self, e):
# Check for __future__ generator_stop and conditionally turn
# a leaking StopIteration into RuntimeError (with its cause
@@ -194,66 +229,9 @@
return self.throw(w_type, w_val, w_tb)
def throw(self, w_type, w_val, w_tb):
+ from pypy.interpreter.pytraceback import check_traceback
space = self.space
- w_yf = self.w_yielded_from
- if w_yf is not None:
- # Paused in a "yield from", pass the throw to the inner generator.
- return self._throw_delegate(space, w_yf, w_type, w_val, w_tb)
- else:
- # Not paused in a "yield from", throw inside this generator
- return self._throw_here(space, w_type, w_val, w_tb)
-
- def _throw_delegate(self, space, w_yf, w_type, w_val, w_tb):
- if space.exception_match(w_type, space.w_GeneratorExit):
- try:
- self.running = True
- try:
- gen_close_iter(space, w_yf)
- finally:
- self.running = False
- except OperationError as e:
- return self.send_error(e)
- return self._throw_here(space, w_type, w_val, w_tb)
- #
- if isinstance(w_yf, GeneratorIterator):
- self.running = True
- try:
- return w_yf.throw(space, w_type, w_val, w_tb)
- except OperationError as e:
- operr = e
- finally:
- self.running = False
- else:
- try:
- w_throw = space.getattr(w_yf, space.wrap("throw"))
- except OperationError as e:
- if not e.match(space, space.w_AttributeError):
- raise
- return self._throw_here(space, w_type, w_val, w_tb)
- self.running = True
- try:
- return space.call_function(w_throw, w_type, w_val, w_tb)
- except OperationError as e:
- operr = e
- finally:
- self.running = False
- # Pop subiterator from stack.
- w_subiter = self.frame.popvalue()
- assert space.is_w(w_subiter, w_yf)
- # Termination repetition of YIELD_FROM
- self.frame.last_instr += 1
- if operr.match(space, space.w_StopIteration):
- operr.normalize_exception(space)
- w_val = space.getattr(operr.get_w_value(space),
- space.wrap("value"))
- return self.send_ex(w_val)
- else:
- return self.send_error(operr)
-
- def _throw_here(self, space, w_type, w_val, w_tb):
- from pypy.interpreter.pytraceback import check_traceback
-
msg = "throw() third argument must be a traceback object"
if space.is_none(w_tb):
tb = None
@@ -278,6 +256,7 @@
space.call_function(space.w_GeneratorExit))
w_yf = self.w_yielded_from
if w_yf is not None:
+ XXX
self.running = True
try:
gen_close_iter(space, w_yf)
@@ -286,14 +265,13 @@
finally:
self.running = False
try:
- w_retval = self.send_error(operr)
+ self.send_error(operr)
except OperationError as e:
if e.match(space, space.w_StopIteration) or \
e.match(space, space.w_GeneratorExit):
return space.w_None
raise
-
- if w_retval is not None:
+ else:
raise oefmt(space.w_RuntimeError,
"%s ignored GeneratorExit", self.KIND)
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -267,6 +267,7 @@
a generator or coroutine frame; in that case, w_arg_or_err
is the input argument -or- an SApplicationException instance.
"""
+ from pypy.interpreter import pyopcode
# the following 'assert' is an annotation hint: it hides from
# the annotator all methods that are defined in PyFrame but
# overridden in the {,Host}FrameClass subclasses of PyFrame.
@@ -281,22 +282,24 @@
#
# Execution starts just after the last_instr. Initially,
# last_instr is -1. After a generator suspends it points to
- # the YIELD_VALUE instruction.
- if in_generator is None:
- assert self.last_instr == -1
- next_instr = 0
- else:
- next_instr = in_generator.resume_execute_frame(
- self, w_arg_or_err)
- next_instr = r_uint(next_instr)
- #
+ # the YIELD_VALUE/YIELD_FROM instruction.
try:
- w_exitvalue = self.dispatch(self.pycode, next_instr,
- executioncontext)
- except Exception:
- executioncontext.return_trace(self, self.space.w_None)
- raise
- executioncontext.return_trace(self, w_exitvalue)
+ if in_generator is None:
+ assert self.last_instr == -1
+ next_instr = 0
+ else:
+ next_instr = in_generator.resume_execute_frame(
+ self, w_arg_or_err)
+ next_instr = r_uint(next_instr)
+ #
+ self.dispatch(self.pycode, next_instr, executioncontext)
+ except pyopcode.Return:
+ self.last_exception = None
+ w_exitvalue = self.popvalue()
+ except pyopcode.Yield:
+ w_exitvalue = self.popvalue()
+ finally:
+ executioncontext.return_trace(self, w_exitvalue)
# it used to say self.last_exception = None
# this is now done by the code in pypyjit module
# since we don't want to invalidate the virtualizable
@@ -898,7 +901,7 @@
return None
def get_generator(self):
- if space.config.translation.rweakref:
+ if self.space.config.translation.rweakref:
return self.f_generator_wref()
else:
return self.f_generator_nowref
@@ -935,7 +938,6 @@
def get_block_class(opname):
# select the appropriate kind of block
- from pypy.interpreter.pyopcode import block_classes
return block_classes[opname]
def unpickle_block(space, w_tup):
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -67,15 +67,8 @@
# For the sequel, force 'next_instr' to be unsigned for performance
next_instr = r_uint(next_instr)
co_code = pycode.co_code
-
- try:
- while True:
- next_instr = self.handle_bytecode(co_code, next_instr, ec)
- except Return:
- self.last_exception = None
- except Yield:
- pass
- return self.popvalue()
+ while True:
+ next_instr = self.handle_bytecode(co_code, next_instr, ec)
def handle_bytecode(self, co_code, next_instr, ec):
try:
@@ -1034,41 +1027,27 @@
raise Yield
def YIELD_FROM(self, oparg, next_instr):
- from pypy.interpreter.astcompiler import consts
+ # Unlike CPython, we handle this not by repeating the same
+ # bytecode over and over until the inner iterator is exhausted.
+ # Instead, we directly set the generator's w_yielded_from.
+ # This asks generator.resume_execute_frame() to exhaust that
+ # sub-iterable first before continuing on the next bytecode.
from pypy.interpreter.generator import Coroutine
- space = self.space
- w_value = self.popvalue()
- w_gen = self.peekvalue()
- if isinstance(w_gen, Coroutine):
- if (w_gen.descr_gi_code(space).co_flags & consts.CO_COROUTINE and
- not self.pycode.co_flags & (consts.CO_COROUTINE |
- consts.CO_ITERABLE_COROUTINE)):
- raise oefmt(self.space.w_TypeError,
- "cannot 'yield from' a coroutine object "
- "from a generator")
- try:
- if space.is_none(w_value):
- w_retval = space.next(w_gen)
- else:
- w_retval = space.call_method(w_gen, "send", w_value)
- except OperationError as e:
- if not e.match(space, space.w_StopIteration):
- raise
- self.popvalue() # Remove iter from stack
- e.normalize_exception(space)
- try:
- w_value = space.getattr(e.get_w_value(space),
space.wrap("value"))
- except OperationError as e:
- if not e.match(space, space.w_AttributeError):
- raise
- w_value = space.w_None
- self.pushvalue(w_value)
- else:
- # iter remains on stack, w_retval is value to be yielded.
- self.pushvalue(w_retval)
- # and repeat...
- self.last_instr = self.last_instr - 1
- raise Yield
+ in_generator = self.get_generator()
+ assert in_generator is not None
+ w_inputvalue = self.popvalue()
+ w_gen = self.popvalue()
+ if isinstance(w_gen, Coroutine) and not isinstance(self, Coroutine):
+ raise oefmt(self.space.w_TypeError,
+ "cannot 'yield from' a coroutine object "
+ "from a generator")
+ #
+ in_generator.w_yielded_from = w_gen
+ in_generator.next_yield_from(self, w_inputvalue)
+ # Common case: the call above raises Yield.
+ # If instead the iterable is empty, next_yield_from() pushed the
+ # final result and returns. In that case, we can just continue
+ # with the next bytecode.
def jump_absolute(self, jumpto, ec):
# this function is overridden by pypy.module.pypyjit.interp_jit
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
@@ -325,6 +325,24 @@
assert False, 'Expected StopIteration'
"""
+ def test_yield_from_basic(self):
+ """
+ def f1():
+ yield from []
+ yield from [1, 2, 3]
+ yield from f2()
+ def f2():
+ yield 4
+ yield 5
+ gen = f1()
+ assert next(gen) == 1
+ assert next(gen) == 2
+ assert next(gen) == 3
+ assert next(gen) == 4
+ assert next(gen) == 5
+ assert list(gen) == []
+ """
+
def test_yield_from_return(self):
"""
def f1():
diff --git a/pypy/module/pypyjit/interp_jit.py
b/pypy/module/pypyjit/interp_jit.py
--- a/pypy/module/pypyjit/interp_jit.py
+++ b/pypy/module/pypyjit/interp_jit.py
@@ -90,6 +90,7 @@
self.valuestackdepth = hint(self.valuestackdepth, promote=True)
next_instr = self.handle_bytecode(co_code, next_instr, ec)
is_being_profiled = self.get_is_being_profiled()
+ XXX XXX # please fix, the base dispatch() changed
except Yield:
# preserve self.last_exception between generator yields
w_result = self.popvalue()
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit