Author: Ronan Lamy <[email protected]>
Branch: py3.6
Changeset: r97796:2ccaa27c8549
Date: 2019-10-16 15:19 +0000
http://bitbucket.org/pypy/pypy/changeset/2ccaa27c8549/
Log: Merged in py3.6-asyncgen (pull request #672)
Fix asyncgen_hooks and refactor coroutine execution
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -1234,8 +1234,11 @@
from pypy.interpreter.generator import GeneratorIterator
return isinstance(w_obj, GeneratorIterator)
+ def callable_w(self, w_obj):
+ return self.lookup(w_obj, "__call__") is not None
+
def callable(self, w_obj):
- return self.newbool(self.lookup(w_obj, "__call__") is not None)
+ return self.newbool(self.callable_w(w_obj))
def issequence_w(self, w_obj):
flag = self.type(w_obj).flag_map_or_seq
diff --git a/pypy/interpreter/executioncontext.py
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -23,8 +23,9 @@
# XXX [fijal] but they're not. is_being_profiled is guarded a bit all
# over the place as well as w_tracefunc
- _immutable_fields_ = ['profilefunc?', 'w_tracefunc?',
- 'w_coroutine_wrapper_fn?']
+ _immutable_fields_ = [
+ 'profilefunc?', 'w_tracefunc?', 'w_coroutine_wrapper_fn?',
+ 'w_asyncgen_firstiter_fn?', 'w_asyncgen_finalizer_fn?']
def __init__(self, space):
self.space = space
@@ -41,6 +42,8 @@
self.thread_disappeared = False # might be set to True after
os.fork()
self.w_coroutine_wrapper_fn = None
self.in_coroutine_wrapper = False
+ self.w_asyncgen_firstiter_fn = None
+ self.w_asyncgen_finalizer_fn = None
@staticmethod
def _mark_thread_disappeared(space):
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -11,8 +11,6 @@
class GeneratorOrCoroutine(W_Root):
_immutable_fields_ = ['pycode']
- w_yielded_from = None
-
def __init__(self, frame, name=None, qualname=None):
self.space = frame.space
self.frame = frame # turned into None when frame_finished_execution
@@ -84,7 +82,7 @@
operr = OperationError(space.w_StopIteration, space.w_None)
raise operr
- w_result = self._invoke_execute_frame(frame, w_arg_or_err)
+ w_result = self._invoke_execute_frame(w_arg_or_err)
assert w_result is not None
# if the frame is now marked as finished, it was RETURNed from
@@ -102,8 +100,9 @@
else:
return w_result # YIELDed
- def _invoke_execute_frame(self, frame, w_arg_or_err):
+ def _invoke_execute_frame(self, w_arg_or_err):
space = self.space
+ frame = self.frame
if self.running:
raise oefmt(space.w_ValueError, "%s already executing", self.KIND)
ec = space.getexecutioncontext()
@@ -126,7 +125,7 @@
self.KIND)
self.running = True
try:
- w_result = frame.execute_frame(self, w_arg_or_err)
+ w_result = frame.execute_frame(w_arg_or_err)
except OperationError as e:
# errors finish a frame
try:
@@ -149,68 +148,19 @@
ec.set_sys_exc_info(current_exc_info)
return w_result
- def resume_execute_frame(self, frame, w_arg_or_err):
- # Called from execute_frame() just before resuming the bytecode
- # interpretation.
- space = self.space
- w_yf = self.w_yielded_from
- if w_yf is not None:
- self.w_yielded_from = None
- try:
- self.next_yield_from(frame, w_yf, w_arg_or_err)
- except OperationError as operr:
- operr.record_context(space, space.getexecutioncontext())
- return frame.handle_generator_error(operr)
- # Normal case: the call above raises Yield.
- # We reach this point if the iterable is exhausted.
- last_instr = jit.promote(frame.last_instr)
- assert last_instr & 1 == 0
- assert last_instr >= 0
- return r_uint(last_instr + 2)
+ def get_delegate(self):
+ if self.frame is None:
+ return None
+ return self.frame.w_yielding_from
- if isinstance(w_arg_or_err, SApplicationException):
- return frame.handle_generator_error(w_arg_or_err.operr)
+ def descr_delegate(self, space):
+ w_yf = self.get_delegate()
+ if w_yf is None:
+ return space.w_None
+ return w_yf
- last_instr = jit.promote(frame.last_instr)
- if last_instr != -1:
- assert last_instr & 1 == 0
- frame.pushvalue(w_arg_or_err)
- return r_uint(last_instr + 2)
- else:
- return r_uint(0)
-
- def next_yield_from(self, frame, w_yf, 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
- try:
- if isinstance(w_yf, GeneratorOrCoroutine):
- w_retval = w_yf.send_ex(w_inputvalue_or_err)
- elif isinstance(w_yf, AsyncGenASend): # performance only
- w_retval = w_yf.do_send(w_inputvalue_or_err)
- elif space.is_w(w_inputvalue_or_err, space.w_None):
- w_retval = space.next(w_yf)
- else:
- w_retval = delegate_to_nongen(space, w_yf, w_inputvalue_or_err)
- except OperationError as e:
- if not e.match(space, space.w_StopIteration):
- raise
- frame._report_stopiteration_sometimes(w_yf, e)
- try:
- w_stop_value = space.getattr(e.get_w_value(space),
- space.newtext("value"))
- except OperationError as e:
- if not e.match(space, space.w_AttributeError):
- raise
- w_stop_value = space.w_None
- frame.pushvalue(w_stop_value)
- return
- else:
- frame.pushvalue(w_retval)
- self.w_yielded_from = w_yf
- raise Yield
+ def set_delegate(self, w_delegate):
+ self.frame.w_yielding_from = w_delegate
def _leak_stopiteration(self, e):
# Check for __future__ generator_stop and conditionally turn
@@ -259,8 +209,8 @@
operr = OperationError(w_type, w_val, tb)
operr.normalize_exception(space)
- # note: w_yielded_from is always None if 'self.running'
- if (self.w_yielded_from is not None and
+ # note: _w_yielded_from is always None if 'self.running'
+ if (self.get_delegate() is not None and
operr.match(space, space.w_GeneratorExit)):
try:
self._gen_close_iter(space)
@@ -276,8 +226,8 @@
def _gen_close_iter(self, space):
assert not self.running
- w_yf = self.w_yielded_from
- self.w_yielded_from = None
+ w_yf = self.get_delegate()
+ self.set_delegate(None)
self.running = True
try:
gen_close_iter(space, w_yf)
@@ -290,8 +240,8 @@
return # nothing to do in this case
space = self.space
operr = None
- # note: w_yielded_from is always None if 'self.running'
- w_yf = self.w_yielded_from
+ # note: _w_yielded_from is always None if 'self.running'
+ w_yf = self.get_delegate()
if w_yf is not None:
try:
self._gen_close_iter(space)
@@ -375,7 +325,7 @@
# generate 2 versions of the function and 2 jit drivers.
def _create_unpack_into():
jitdriver = jit.JitDriver(greens=['pycode'],
- reds=['self', 'frame', 'results'],
+ reds='auto',
name='unpack_into')
def unpack_into(self, results):
@@ -386,12 +336,10 @@
return
pycode = self.pycode
while True:
- jitdriver.jit_merge_point(self=self, frame=frame,
- results=results, pycode=pycode)
+ jitdriver.jit_merge_point(pycode=pycode)
space = self.space
try:
- w_result = self._invoke_execute_frame(
- frame, space.w_None)
+ w_result = self._invoke_execute_frame(space.w_None)
except OperationError as e:
if not e.match(space, space.w_StopIteration):
raise
@@ -489,27 +437,6 @@
else:
space.call_function(w_close)
-def delegate_to_nongen(space, w_yf, w_inputvalue_or_err):
- # invoke a "send" or "throw" by method name to a non-generator w_yf
- if isinstance(w_inputvalue_or_err, SApplicationException):
- operr = w_inputvalue_or_err.operr
- try:
- w_meth = space.getattr(w_yf, space.newtext("throw"))
- except OperationError as e:
- if not e.match(space, space.w_AttributeError):
- raise
- raise operr
- # bah, CPython calls here with the exact same arguments as
- # originally passed to throw(). In our case it is far removed.
- # Let's hope nobody will complain...
- operr.normalize_exception(space)
- w_exc = operr.w_type
- w_val = operr.get_w_value(space)
- w_tb = operr.get_w_traceback(space)
- return space.call_function(w_meth, w_exc, w_val, w_tb)
- else:
- return space.call_method(w_yf, "send", w_inputvalue_or_err)
-
def gen_is_coroutine(w_obj):
return (isinstance(w_obj, GeneratorIterator) and
(w_obj.pycode.co_flags & consts.CO_ITERABLE_COROUTINE) != 0)
@@ -594,15 +521,15 @@
if self.hooks_inited:
return
self.hooks_inited = True
-
- self.w_finalizer = self.space.appexec([], '''():
- import sys
- hooks = sys.get_asyncgen_hooks()
- return hooks.finalizer''')
+ ec = self.space.getexecutioncontext()
+ self.w_finalizer = ec.w_asyncgen_finalizer_fn
+ w_firstiter = ec.w_asyncgen_firstiter_fn
+ if w_firstiter is not None:
+ self.space.call_function(w_firstiter, self)
def _finalize_(self):
if self.frame is not None and self.frame.lastblock is not None:
- if self.w_finalizer is not self.space.w_None:
+ if self.w_finalizer is not None:
# XXX: this is a hack to resurrect the weakref that was cleared
# before running _finalize_()
if self.space.config.translation.rweakref:
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -68,6 +68,7 @@
frame_finished_execution = False
f_generator_wref = rweakref.dead_ref # for generators/coroutines
f_generator_nowref = None # (only one of the two attrs)
+ w_yielding_from = None
last_instr = -1
f_backref = jit.vref_None
@@ -319,9 +320,40 @@
ec.in_coroutine_wrapper = False
return w_gen
- def execute_frame(self, in_generator=None, w_arg_or_err=None):
+ def resume_execute_frame(self, w_arg_or_err):
+ # Called from execute_frame() just before resuming the bytecode
+ # interpretation.
+ from pypy.interpreter.pyopcode import SApplicationException
+ space = self.space
+ w_yf = self.w_yielding_from
+ if w_yf is not None:
+ self.w_yielding_from = None
+ try:
+ self.next_yield_from(w_yf, w_arg_or_err)
+ except OperationError as operr:
+ operr.record_context(space, space.getexecutioncontext())
+ return self.handle_generator_error(operr)
+ # Normal case: the call above raises Yield.
+ # We reach this point if the iterable is exhausted.
+ last_instr = jit.promote(self.last_instr)
+ assert last_instr & 1 == 0
+ assert last_instr >= 0
+ return r_uint(last_instr + 2)
+
+ if isinstance(w_arg_or_err, SApplicationException):
+ return self.handle_generator_error(w_arg_or_err.operr)
+
+ last_instr = jit.promote(self.last_instr)
+ if last_instr != -1:
+ assert last_instr & 1 == 0
+ self.pushvalue(w_arg_or_err)
+ return r_uint(last_instr + 2)
+ else:
+ return r_uint(0)
+
+ def execute_frame(self, w_arg_or_err=None):
"""Execute this frame. Main entry point to the interpreter.
- 'in_generator' is non-None iff we are starting or resuming
+ 'w_arg_or_err' is non-None iff we are starting or resuming
a generator or coroutine frame; in that case, w_arg_or_err
is the input argument -or- an SApplicationException instance.
"""
@@ -343,12 +375,11 @@
# the YIELD_VALUE/YIELD_FROM instruction.
try:
try:
- if in_generator is None:
+ if w_arg_or_err is None:
assert self.last_instr == -1
next_instr = r_uint(0)
else:
- next_instr = in_generator.resume_execute_frame(
- self, w_arg_or_err)
+ next_instr = self.resume_execute_frame(w_arg_or_err)
except pyopcode.Yield:
w_exitvalue = self.popvalue()
else:
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -1113,24 +1113,51 @@
self.pushvalue(w_value)
raise Yield
+ def next_yield_from(self, w_yf, 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.
+ """
+ from pypy.interpreter.generator import (
+ GeneratorOrCoroutine, AsyncGenASend)
+ space = self.space
+ try:
+ if isinstance(w_yf, GeneratorOrCoroutine):
+ w_retval = w_yf.send_ex(w_inputvalue_or_err)
+ elif isinstance(w_yf, AsyncGenASend): # performance only
+ w_retval = w_yf.do_send(w_inputvalue_or_err)
+ elif space.is_w(w_inputvalue_or_err, space.w_None):
+ w_retval = space.next(w_yf)
+ else:
+ w_retval = delegate_to_nongen(space, w_yf, w_inputvalue_or_err)
+ except OperationError as e:
+ if not e.match(space, space.w_StopIteration):
+ raise
+ self._report_stopiteration_sometimes(w_yf, e)
+ try:
+ w_stop_value = space.getattr(e.get_w_value(space),
+ space.newtext("value"))
+ except OperationError as e:
+ if not e.match(space, space.w_AttributeError):
+ raise
+ w_stop_value = space.w_None
+ self.pushvalue(w_stop_value)
+ return
+ else:
+ self.pushvalue(w_retval)
+ self.w_yielding_from = w_yf
+ raise Yield
+
def YIELD_FROM(self, oparg, next_instr):
# 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
+ # Instead, we set w_yielding_from.
+ # This asks resume_execute_frame() to exhaust that
# sub-iterable first before continuing on the next bytecode.
- in_generator = self.get_generator()
- if in_generator is None:
- # Issue #2768: rare case involving __del__ methods.
- # XXX This is a workaround, not a proper solution.
- raise oefmt(self.space.w_RuntimeError,
- "PyPy limitation: cannot use 'yield from' or 'await' "
- "in a generator/coroutine that has been partially "
- "deallocated already, typically seen via __del__")
w_inputvalue = self.popvalue() # that's always w_None, actually
w_gen = self.popvalue()
#
- in_generator.next_yield_from(self, w_gen, w_inputvalue)
+ self.next_yield_from(w_gen, 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
@@ -1556,7 +1583,7 @@
w_iterable = self.popvalue()
w_iter = get_awaitable_iter(self.space, w_iterable)
if isinstance(w_iter, Coroutine):
- if w_iter.w_yielded_from is not None:
+ if w_iter.get_delegate() is not None:
# 'w_iter' is a coroutine object that is being awaited,
# '.w_yielded_from' is the current awaitable being awaited on.
raise oefmt(self.space.w_RuntimeError,
@@ -1694,6 +1721,27 @@
else:
self.MISSING_OPCODE(oparg, next_instr)
+def delegate_to_nongen(space, w_yf, w_inputvalue_or_err):
+ # invoke a "send" or "throw" by method name to a non-generator w_yf
+ if isinstance(w_inputvalue_or_err, SApplicationException):
+ operr = w_inputvalue_or_err.operr
+ try:
+ w_meth = space.getattr(w_yf, space.newtext("throw"))
+ except OperationError as e:
+ if not e.match(space, space.w_AttributeError):
+ raise
+ raise operr
+ # bah, CPython calls here with the exact same arguments as
+ # originally passed to throw(). In our case it is far removed.
+ # Let's hope nobody will complain...
+ operr.normalize_exception(space)
+ w_exc = operr.w_type
+ w_val = operr.get_w_value(space)
+ w_tb = operr.get_w_traceback(space)
+ return space.call_function(w_meth, w_exc, w_val, w_tb)
+ else:
+ return space.call_method(w_yf, "send", w_inputvalue_or_err)
+
### ____________________________________________________________ ###
diff --git a/pypy/interpreter/test/apptest_coroutine.py
b/pypy/interpreter/test/apptest_coroutine.py
--- a/pypy/interpreter/test/apptest_coroutine.py
+++ b/pypy/interpreter/test/apptest_coroutine.py
@@ -633,16 +633,12 @@
a2 = g.aclose()
sys.set_asyncgen_hooks(finalizer=_finalize)
assert state == 0
- try:
+ with pytest.raises(StopIteration):
a.send(None)
- except StopIteration:
- pass
assert a2.send(None) == 'coro'
assert state == 1
- try:
+ with pytest.raises(StopIteration):
a2.send(None)
- except StopIteration:
- pass
assert state == 2
sys.set_asyncgen_hooks(None, None)
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -838,7 +838,7 @@
gi_running = interp_attrproperty('running', cls=GeneratorIterator,
wrapfn="newbool"),
gi_frame = GetSetProperty(GeneratorIterator.descr_gicr_frame),
gi_code = interp_attrproperty_w('pycode', cls=GeneratorIterator),
- gi_yieldfrom=interp_attrproperty_w('w_yielded_from',
cls=GeneratorIterator),
+ gi_yieldfrom=GetSetProperty(GeneratorIterator.descr_delegate),
__name__ = GetSetProperty(GeneratorIterator.descr__name__,
GeneratorIterator.descr_set__name__),
__qualname__ = GetSetProperty(GeneratorIterator.descr__qualname__,
@@ -862,7 +862,7 @@
cr_running = interp_attrproperty('running', cls=Coroutine,
wrapfn="newbool"),
cr_frame = GetSetProperty(Coroutine.descr_gicr_frame),
cr_code = interp_attrproperty_w('pycode', cls=Coroutine),
- cr_await = interp_attrproperty_w('w_yielded_from', cls=Coroutine),
+ cr_await=GetSetProperty(Coroutine.descr_delegate),
__name__ = GetSetProperty(Coroutine.descr__name__,
Coroutine.descr_set__name__,
doc="name of the coroutine"),
@@ -890,7 +890,7 @@
ag_running = interp_attrproperty('running', cls=AsyncGenerator,
wrapfn="newbool"),
ag_frame = GetSetProperty(AsyncGenerator.descr_gicr_frame),
ag_code = interp_attrproperty_w('pycode', cls=AsyncGenerator),
- ag_await = interp_attrproperty_w('w_yielded_from', cls=AsyncGenerator),
+ ag_await=GetSetProperty(AsyncGenerator.descr_delegate),
__name__ = GetSetProperty(AsyncGenerator.descr__name__,
AsyncGenerator.descr_set__name__,
doc="name of the async generator"),
diff --git a/pypy/module/_vmprof/interp_vmprof.py
b/pypy/module/_vmprof/interp_vmprof.py
--- a/pypy/module/_vmprof/interp_vmprof.py
+++ b/pypy/module/_vmprof/interp_vmprof.py
@@ -9,15 +9,15 @@
# ____________________________________________________________
-_get_code = lambda frame, w_inputvalue, operr: frame.pycode
+_get_code = lambda frame, w_arg_or_err: frame.pycode
_decorator = rvmprof.vmprof_execute_code("pypy", _get_code, W_Root)
my_execute_frame = _decorator(PyFrame.execute_frame)
class __extend__(PyFrame):
- def execute_frame(self, in_generator=None, w_arg_or_err=None):
+ def execute_frame(self, w_arg_or_err=None):
# indirection for the optional arguments
- return my_execute_frame(self, in_generator, w_arg_or_err)
+ return my_execute_frame(self, w_arg_or_err)
def _safe(s):
diff --git a/pypy/module/sys/app.py b/pypy/module/sys/app.py
--- a/pypy/module/sys/app.py
+++ b/pypy/module/sys/app.py
@@ -75,7 +75,7 @@
If it is another kind of object, it will be printed and the system
exit status will be one (i.e., failure)."""
# note that we cannot simply use SystemExit(exitcode) here.
- # in the default branch, we use "raise SystemExit, exitcode",
+ # in the default branch, we use "raise SystemExit, exitcode",
# which leads to an extra de-tupelizing
# in normalize_exception, which is exactly like CPython's.
if isinstance(exitcode, tuple):
@@ -109,7 +109,6 @@
# This is tested in test_app_main.py
class sysflags(metaclass=structseqtype):
-
name = "sys.flags"
debug = structseqfield(0)
@@ -130,29 +129,6 @@
null__xoptions = {}
-class asyncgen_hooks(metaclass=structseqtype):
- name = "asyncgen_hooks"
-
- firstiter = structseqfield(0)
- finalizer = structseqfield(1)
-
-# FIXME: make this thread-local
-_current_asyncgen_hooks = asyncgen_hooks((None, None))
-
-def get_asyncgen_hooks():
- return _current_asyncgen_hooks
-
-_default_arg = object()
-
-def set_asyncgen_hooks(firstiter=_default_arg, finalizer=_default_arg):
- global _current_asyncgen_hooks
- if firstiter is _default_arg:
- firstiter = _current_asyncgen_hooks.firstiter
- if finalizer is _default_arg:
- finalizer = _current_asyncgen_hooks.finalizer
- _current_asyncgen_hooks = asyncgen_hooks((firstiter, finalizer))
-
-
implementation = SimpleNamespace(
name='pypy',
version=sys.version_info,
diff --git a/pypy/module/sys/moduledef.py b/pypy/module/sys/moduledef.py
--- a/pypy/module/sys/moduledef.py
+++ b/pypy/module/sys/moduledef.py
@@ -95,6 +95,8 @@
'get_coroutine_wrapper' : 'vm.get_coroutine_wrapper',
'set_coroutine_wrapper' : 'vm.set_coroutine_wrapper',
+ 'get_asyncgen_hooks' : 'vm.get_asyncgen_hooks',
+ 'set_asyncgen_hooks' : 'vm.set_asyncgen_hooks',
'is_finalizing' : 'vm.is_finalizing',
}
@@ -115,8 +117,6 @@
'flags' : 'app.null_sysflags',
'_xoptions' : 'app.null__xoptions',
'implementation' : 'app.implementation',
- 'get_asyncgen_hooks' : 'app.get_asyncgen_hooks',
- 'set_asyncgen_hooks' : 'app.set_asyncgen_hooks',
# these six attributes are here only during tests;
# they are removed before translation
@@ -184,7 +184,7 @@
if w_file is w_stdout:
e.write_unraisable(space, '', w_file)
ret = -1
- return ret
+ return ret
def _file_is_closed(self, space, w_file):
try:
diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py
--- a/pypy/module/sys/vm.py
+++ b/pypy/module/sys/vm.py
@@ -7,7 +7,7 @@
from pypy.interpreter import gateway
from pypy.interpreter.error import oefmt
-from pypy.interpreter.gateway import unwrap_spec
+from pypy.interpreter.gateway import unwrap_spec, WrappedDefault
# ____________________________________________________________
@@ -240,6 +240,14 @@
suite_mask = structseqfield(12, "Bit mask identifying available product
suites")
product_type = structseqfield(13, "System product type")
platform_version = structseqfield(14, "Diagnostic version number")
+
+
+class asyncgen_hooks(metaclass=structseqtype):
+ name = "asyncgen_hooks"
+
+ firstiter = structseqfield(0)
+ finalizer = structseqfield(1)
+
''')
@@ -286,7 +294,7 @@
getsizeof_missing = """getsizeof(...)
getsizeof(object, default) -> int
-
+
Return the size of object in bytes.
sys.getsizeof(object, default) will always return default on PyPy, and
@@ -351,5 +359,45 @@
else:
raise oefmt(space.w_TypeError, "callable expected, got %T", w_wrapper)
+def get_asyncgen_hooks(space):
+ """get_asyncgen_hooks()
+
+Return a namedtuple of installed asynchronous generators hooks (firstiter,
finalizer)."""
+ ec = space.getexecutioncontext()
+ w_firstiter = ec.w_asyncgen_firstiter_fn
+ if w_firstiter is None:
+ w_firstiter = space.w_None
+ w_finalizer = ec.w_asyncgen_finalizer_fn
+ if w_finalizer is None:
+ w_finalizer = space.w_None
+ w_asyncgen_hooks = app.wget(space, "asyncgen_hooks")
+ return space.call_function(
+ w_asyncgen_hooks,
+ space.newtuple([w_firstiter, w_finalizer]))
+
+# Note: the docstring is wrong on CPython
+def set_asyncgen_hooks(space, w_firstiter=None, w_finalizer=None):
+ """set_asyncgen_hooks(firstiter=None, finalizer=None)
+
+Set a finalizer for async generators objects."""
+ ec = space.getexecutioncontext()
+ if space.is_w(w_finalizer, space.w_None):
+ ec.w_asyncgen_finalizer_fn = None
+ elif w_finalizer is not None:
+ if space.callable_w(w_finalizer):
+ ec.w_asyncgen_finalizer_fn = w_finalizer
+ else:
+ raise oefmt(space.w_TypeError,
+ "callable finalizer expected, got %T", w_finalizer)
+ if space.is_w(w_firstiter, space.w_None):
+ ec.w_asyncgen_firstiter_fn = None
+ elif w_firstiter is not None:
+ if space.callable_w(w_firstiter):
+ ec.w_asyncgen_firstiter_fn = w_firstiter
+ else:
+ raise oefmt(space.w_TypeError,
+ "callable firstiter expected, got %T", w_firstiter)
+
+
def is_finalizing(space):
return space.newbool(space.sys.finalizing)
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit