Author: Armin Rigo <ar...@tunes.org> 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 pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit