Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/pycore_pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extern int _Py_IsLocaleCoercionTarget(const char *ctype_loc);
extern void _Py_InitVersion(void);
extern PyStatus _PyFaulthandler_Init(int enable);
extern PyObject * _PyBuiltin_Init(PyInterpreterState *interp);
extern int _PyBuiltin_InitPythonFunctions(PyObject *dict);
extern PyStatus _PySys_Create(
PyThreadState *tstate,
PyObject **sysmod_p);
Expand Down
42 changes: 42 additions & 0 deletions Lib/_builtins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Builtins implemented in Python.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should name it Lib/_pybuiltins.py like we do for decimal and datetime. I think _builtins.py is fairly common and I fear that we will break packages... More generally, I am sad that we don't have a way to reserve names for the stdlib (like having a std namespace)...


This module is frozen into the interpreter and imported during startup,
before the import system exists. The names listed in ``__all__`` are
copied into the ``builtins`` module.
"""

__all__ = ['anext']

_NOT_GIVEN = object()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the sentinel() object here or is it not possible because it's not yet available?



def anext(async_iterator, default=_NOT_GIVEN, /):
"""Return the next item from the async iterator.

If default is given and the async iterator is exhausted,
it is returned instead of raising StopAsyncIteration.
"""
cls = type(async_iterator)
try:
# Looked up on the type, like the C slot am_anext.
anext_method = cls.__anext__
except AttributeError:
raise TypeError(
f"'{cls.__name__}' object is not an async iterator"
) from None
awaitable = anext_method(async_iterator)
if default is _NOT_GIVEN:
return awaitable
return _anext_with_default(awaitable, default)


async def _anext_with_default(awaitable, default):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this additional function?

try:
return await awaitable
except StopAsyncIteration:
return default


for _name in __all__:
globals()[_name].__module__ = 'builtins'
del _name
40 changes: 40 additions & 0 deletions Lib/test/test_asyncio/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,46 @@ async def main():
'async generator CallStackTestBase.test_stack_async_gen.<locals>.gen()',
stack_for_gen_nested_call[1])

async def test_stack_anext_default(self):
# anext() with a default wraps the awaitable in a coroutine, so the
# call graph of a suspended task sees through it into __anext__().

loop = asyncio.get_running_loop()
blocker = loop.create_future()

async def inner():
await blocker

class AIter:
def __aiter__(self):
return self

async def __anext__(self):
await inner()
return 1

async def main():
await anext(AIter(), None)

task = asyncio.create_task(main(), name='anext task')
await asyncio.sleep(0)
try:
stack = capture_test_stack(fut=task)
finally:
blocker.set_result(None)
await task

self.assertEqual(stack[0], [
'T<anext task>',
[
'a inner',
'a __anext__',
'a _anext_with_default',
'a main',
],
[]
])

def test_ag_frame_used_for_async_generator(self):
# Regression test for gh-148736: the ag_await branch of
# _build_graph_for_future must read ag_frame, not cr_frame.
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_importlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def import_importlib(module_name):
fresh = ('importlib',) if '.' in module_name else ()
frozen = import_helper.import_fresh_module(module_name)
source = import_helper.import_fresh_module(module_name, fresh=fresh,
blocked=('_frozen_importlib', '_frozen_importlib_external'))
blocked=('_frozen_importlib', '_frozen_importlib_external',
'_builtins'))
return {'Frozen': frozen, 'Source': source}


Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6174,7 +6174,7 @@ def test_builtins_have_signatures(self):
"next", "vars"}
no_signature |= needs_groups
# These have unrepresentable parameter default values of NULL
unsupported_signature = {"anext", "aiter", "iter"}
unsupported_signature = {"aiter", "iter"}
# These need *args support in Argument Clinic
needs_varargs = {"min", "max", "__build_class__"}
no_signature |= needs_varargs
Expand Down
8 changes: 7 additions & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1623,7 +1623,8 @@ Programs/_testembed: Programs/_testembed.o $(LINK_PYTHON_DEPS)
BOOTSTRAP_HEADERS = \
Python/frozen_modules/importlib._bootstrap.h \
Python/frozen_modules/importlib._bootstrap_external.h \
Python/frozen_modules/zipimport.h
Python/frozen_modules/zipimport.h \
Python/frozen_modules/_builtins.h

Programs/_bootstrap_python.o: Programs/_bootstrap_python.c $(BOOTSTRAP_HEADERS) $(PYTHON_HEADERS)

Expand Down Expand Up @@ -1664,6 +1665,7 @@ FROZEN_FILES_IN = \
Lib/importlib/_bootstrap.py \
Lib/importlib/_bootstrap_external.py \
Lib/zipimport.py \
Lib/_builtins.py \
Lib/abc.py \
Lib/codecs.py \
Lib/io.py \
Expand All @@ -1690,6 +1692,7 @@ FROZEN_FILES_OUT = \
Python/frozen_modules/importlib._bootstrap.h \
Python/frozen_modules/importlib._bootstrap_external.h \
Python/frozen_modules/zipimport.h \
Python/frozen_modules/_builtins.h \
Python/frozen_modules/abc.h \
Python/frozen_modules/codecs.h \
Python/frozen_modules/io.h \
Expand Down Expand Up @@ -1735,6 +1738,9 @@ Python/frozen_modules/importlib._bootstrap_external.h: Lib/importlib/_bootstrap_
Python/frozen_modules/zipimport.h: Lib/zipimport.py $(FREEZE_MODULE_BOOTSTRAP_DEPS)
$(FREEZE_MODULE_BOOTSTRAP) zipimport $(srcdir)/Lib/zipimport.py Python/frozen_modules/zipimport.h

Python/frozen_modules/_builtins.h: Lib/_builtins.py $(FREEZE_MODULE_BOOTSTRAP_DEPS)
$(FREEZE_MODULE_BOOTSTRAP) _builtins $(srcdir)/Lib/_builtins.py Python/frozen_modules/_builtins.h

Python/frozen_modules/abc.h: Lib/abc.py $(FREEZE_MODULE_DEPS)
$(FREEZE_MODULE) abc $(srcdir)/Lib/abc.py Python/frozen_modules/abc.h

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Implement :func:`anext` in Python instead of C, in a frozen ``_builtins``
module. The awaitable returned by ``anext(it, default)`` is now a plain
coroutine, so introspection tools such as :func:`asyncio.print_call_graph`
can see through it into :meth:`~object.__anext__`.
182 changes: 0 additions & 182 deletions Objects/iterobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,33 +403,6 @@ PyTypeObject PyCallIter_Type = {

/* -------------------------------------- */

typedef struct {
PyObject_HEAD
PyObject *wrapped;
PyObject *default_value;
} anextawaitableobject;

#define anextawaitableobject_CAST(op) ((anextawaitableobject *)(op))

static void
anextawaitable_dealloc(PyObject *op)
{
anextawaitableobject *obj = anextawaitableobject_CAST(op);
_PyObject_GC_UNTRACK(obj);
Py_XDECREF(obj->wrapped);
Py_XDECREF(obj->default_value);
PyObject_GC_Del(obj);
}

static int
anextawaitable_traverse(PyObject *op, visitproc visit, void *arg)
{
anextawaitableobject *obj = anextawaitableobject_CAST(op);
Py_VISIT(obj->wrapped);
Py_VISIT(obj->default_value);
return 0;
}

static PyObject *
awaitable_getiter(PyObject *owner, PyObject *wrapped)
{
Expand Down Expand Up @@ -461,99 +434,6 @@ awaitable_getiter(PyObject *owner, PyObject *wrapped)
return awaitable;
}

static PyObject *
anextawaitable_iternext(PyObject *op)
{
/* Consider the following class:
*
* class A:
* async def __anext__(self):
* ...
* a = A()
*
* Then `await anext(a)` should call
* a.__anext__().__await__().__next__()
*
* On the other hand, given
*
* async def agen():
* yield 1
* yield 2
* gen = agen()
*
* Then `await anext(gen)` can just call
* gen.__anext__().__next__()
*/
anextawaitableobject *obj = anextawaitableobject_CAST(op);
PyObject *awaitable = awaitable_getiter(op, obj->wrapped);
if (awaitable == NULL) {
return NULL;
}
PyObject *result = (*Py_TYPE(awaitable)->tp_iternext)(awaitable);
Py_DECREF(awaitable);
if (result != NULL) {
return result;
}
if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) {
PyErr_Clear();
_PyGen_SetStopIterationValue(obj->default_value);
}
return NULL;
}


static PyObject *
anextawaitable_proxy(anextawaitableobject *obj, char *meth, PyObject *arg)
{
PyObject *awaitable = awaitable_getiter((PyObject *)obj, obj->wrapped);
if (awaitable == NULL) {
return NULL;
}
// When specified, 'arg' may be a tuple (if coming from a METH_VARARGS
// method) or a single object (if coming from a METH_O method).
PyObject *ret = arg == NULL
? PyObject_CallMethod(awaitable, meth, NULL)
: PyObject_CallMethod(awaitable, meth, "O", arg);
Py_DECREF(awaitable);
if (ret != NULL) {
return ret;
}
if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) {
/* `anextawaitableobject` is only used by `anext()` when
* a default value is provided. So when we have a StopAsyncIteration
* exception we replace it with a `StopIteration(default)`, as if
* it was the return value of `__anext__()` coroutine.
*/
PyErr_Clear();
_PyGen_SetStopIterationValue(obj->default_value);
}
return NULL;
}


static PyObject *
anextawaitable_send(PyObject *op, PyObject *arg)
{
anextawaitableobject *obj = anextawaitableobject_CAST(op);
return anextawaitable_proxy(obj, "send", arg);
}


static PyObject *
anextawaitable_throw(PyObject *op, PyObject *args)
{
anextawaitableobject *obj = anextawaitableobject_CAST(op);
return anextawaitable_proxy(obj, "throw", args);
}


static PyObject *
anextawaitable_close(PyObject *op, PyObject *Py_UNUSED(dummy))
{
anextawaitableobject *obj = anextawaitableobject_CAST(op);
return anextawaitable_proxy(obj, "close", NULL);
}


PyDoc_STRVAR(send_doc,
"send(arg) -> send 'arg' into the wrapped iterator,\n\
Expand All @@ -574,68 +454,6 @@ PyDoc_STRVAR(close_doc,
"close() -> raise GeneratorExit inside generator.");


static PyMethodDef anextawaitable_methods[] = {
{"send", anextawaitable_send, METH_O, send_doc},
{"throw", anextawaitable_throw, METH_VARARGS, throw_doc},
{"close", anextawaitable_close, METH_NOARGS, close_doc},
{NULL, NULL} /* Sentinel */
};


static PyAsyncMethods anextawaitable_as_async = {
PyObject_SelfIter, /* am_await */
0, /* am_aiter */
0, /* am_anext */
0, /* am_send */
};

PyTypeObject _PyAnextAwaitable_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"anext_awaitable", /* tp_name */
sizeof(anextawaitableobject), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
anextawaitable_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
&anextawaitable_as_async, /* tp_as_async */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
0, /* tp_doc */
anextawaitable_traverse, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
anextawaitable_iternext, /* tp_iternext */
anextawaitable_methods, /* tp_methods */
};

PyObject *
PyAnextAwaitable_New(PyObject *awaitable, PyObject *default_value)
{
anextawaitableobject *anext = PyObject_GC_New(
anextawaitableobject, &_PyAnextAwaitable_Type);
if (anext == NULL) {
return NULL;
}
anext->wrapped = Py_NewRef(awaitable);
anext->default_value = Py_NewRef(default_value);
_PyObject_GC_TRACK(anext);
return (PyObject *)anext;
}


/* -------------------------------------- */

/* The asynchronous counterpart of calliterobject: the callable is called
Expand Down
2 changes: 0 additions & 2 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2522,7 +2522,6 @@ _PyObject_FiniState(PyInterpreterState *interp)

extern PyTypeObject _PyACallIter_Type;
extern PyTypeObject _PyACallIterAwaitable_Type;
extern PyTypeObject _PyAnextAwaitable_Type;
extern PyTypeObject _PyLegacyEventHandler_Type;
extern PyTypeObject _PyLineIterator;
extern PyTypeObject _PyMemoryIter_Type;
Expand Down Expand Up @@ -2617,7 +2616,6 @@ static PyTypeObject* static_types[_Py_NUM_MANAGED_PREINITIALIZED_TYPES] = {
&Py_GenericAliasType,
&_PyACallIter_Type,
&_PyACallIterAwaitable_Type,
&_PyAnextAwaitable_Type,
&_PyAsyncGenASend_Type,
&_PyAsyncGenAThrow_Type,
&_PyAsyncGenWrappedValue_Type,
Expand Down
5 changes: 5 additions & 0 deletions PCbuild/_freeze_module.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@
<IntFile>$(IntDir)zipimport.g.h</IntFile>
<OutFile>$(GeneratedFrozenModulesDir)Python\frozen_modules\zipimport.h</OutFile>
</None>
<None Include="..\Lib\_builtins.py">
<ModName>_builtins</ModName>
<IntFile>$(IntDir)_builtins.g.h</IntFile>
<OutFile>$(GeneratedFrozenModulesDir)Python\frozen_modules\_builtins.h</OutFile>
</None>
<None Include="..\Lib\abc.py">
<ModName>abc</ModName>
<IntFile>$(IntDir)abc.g.h</IntFile>
Expand Down
3 changes: 3 additions & 0 deletions PCbuild/_freeze_module.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,9 @@
<None Include="..\Lib\zipimport.py">
<Filter>Python Files</Filter>
</None>
<None Include="..\Lib\_builtins.py">
<Filter>Python Files</Filter>
</None>
<None Include="..\Lib\abc.py">
<Filter>Python Files</Filter>
</None>
Expand Down
Loading
Loading