Skip to content
Closed
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
2 changes: 2 additions & 0 deletions Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,8 @@ struct _is {

PyObject *sysdict_copy;
PyObject *builtins_copy;
// _pybuiltins._anext_with_default(), used by anext() with a default.
PyObject *anext_with_default;
// Initialized to _PyEval_EvalFrameDefault().
_PyFrameEvalFunction eval_frame;
int eval_frame_allow_specialization;
Expand Down
30 changes: 2 additions & 28 deletions Lib/_pybuiltins.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,12 @@
copied into the ``builtins`` module.
"""

__all__ = ['anext']

_NOT_GIVEN = sentinel("_NOT_GIVEN")


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__!r} 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)
__all__ = []


# Used by the C implementation of anext() when a default is given.
async def _anext_with_default(awaitable, default):
try:
return await awaitable
except StopAsyncIteration:
return default


for _name in __all__:
globals()[_name].__module__ = 'builtins'
del _name
14 changes: 4 additions & 10 deletions Lib/test/test_asyncgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,9 +1048,10 @@ async def do_test():
self.assertEqual(result, "completed")

def test_anext_traceback_filename(self):
# anext() is implemented in Python in Lib/_pybuiltins.py, which is
# frozen under the builtins ID, so its frames name builtins rather
# than the module they are frozen from.
# The coroutine awaited by anext() with a default is implemented in
# Python in Lib/_pybuiltins.py, which is frozen under the builtins
# ID, so its frames name builtins rather than the module they are
# frozen from.
def filenames(exc):
return [frame.filename
for frame in traceback.extract_tb(exc.__traceback__)]
Expand All @@ -1063,13 +1064,6 @@ async def __anext__(self):

# assertRaises() drops the traceback, so catch the exceptions here.
async def do_test():
try:
anext(42, "default")
except TypeError as exc:
self.assertIn("<frozen builtins>", filenames(exc))
else:
self.fail("TypeError was not raised")

try:
await anext(AIter(), "default")
except ZeroDivisionError as exc:
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 @@ -6229,7 +6229,7 @@ def test_builtins_have_signatures(self):
needs_groups = {"range", "slice", "getattr", "next"}
no_signature |= needs_groups
# These have unrepresentable parameter default values of NULL
unsupported_signature = {"aiter", "iter", "dir", "vars"}
unsupported_signature = {"anext", "aiter", "iter", "dir", "vars"}
# These need *args support in Argument Clinic
needs_varargs = {"min", "max", "__build_class__"}
no_signature |= needs_varargs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a performance regression in :func:`anext` introduced in :gh:`157361`.
The one-argument form is implemented in C again and calls the async
iterator's :meth:`~object.__anext__` slot directly.
58 changes: 57 additions & 1 deletion Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1963,6 +1963,53 @@ builtin_aiter_impl(PyObject *module, PyObject *object, PyObject *stop_value,
return _PyACallIter_New(object, stop_value, stop_exception);
}

/*[clinic input]
anext as builtin_anext

async_iterator as aiterator: object
default: object = NULL
/

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.
[clinic start generated code]*/

static PyObject *
builtin_anext_impl(PyObject *module, PyObject *aiterator,
PyObject *default_value)
/*[clinic end generated code: output=f02c060c163a81fa input=f3dc5a93f073e5ac]*/
{
PyTypeObject *t = Py_TYPE(aiterator);
if (t->tp_as_async == NULL || t->tp_as_async->am_anext == NULL) {
PyErr_Format(PyExc_TypeError,
"'%.200s' object is not an async iterator",
t->tp_name);
return NULL;
}

PyObject *awaitable = (*t->tp_as_async->am_anext)(aiterator);
if (awaitable == NULL || default_value == NULL) {
return awaitable;
}

/* gh-157361: the default is handled by a Python coroutine so that
introspection tools can see through it into the awaitable. */
PyObject *helper = _PyInterpreterState_GET()->anext_with_default;
if (helper == NULL) {
Py_DECREF(awaitable);
PyErr_SetString(PyExc_RuntimeError,
"anext() with a default is not available");
return NULL;
}
PyObject *args[2] = {awaitable, default_value};
PyObject *res = PyObject_Vectorcall(helper, args, 2, NULL);
Py_DECREF(awaitable);
return res;
}


/*[clinic input]
len as builtin_len

Expand Down Expand Up @@ -3461,6 +3508,7 @@ static PyMethodDef builtin_methods[] = {
{"max", _PyCFunction_CAST(builtin_max), METH_FASTCALL | METH_KEYWORDS, max_doc},
{"min", _PyCFunction_CAST(builtin_min), METH_FASTCALL | METH_KEYWORDS, min_doc},
{"next", _PyCFunction_CAST(builtin_next), METH_FASTCALL, next_doc},
BUILTIN_ANEXT_METHODDEF
BUILTIN_OCT_METHODDEF
BUILTIN_ORD_METHODDEF
BUILTIN_POW_METHODDEF
Expand Down Expand Up @@ -3504,7 +3552,8 @@ static struct PyModuleDef builtinsmodule = {
Lib/_pybuiltins.py is frozen into the interpreter as a bootstrap module
(see Tools/build/freeze_modules.py), so it can be imported here before
the import system exists. The names in its __all__ are copied into the
builtins dict. */
builtins dict, and helpers used by C builtins are kept on the
interpreter. */

int
_PyBuiltin_InitPythonFunctions(PyObject *dict)
Expand Down Expand Up @@ -3542,6 +3591,13 @@ _PyBuiltin_InitPythonFunctions(PyObject *dict)
goto done;
}
}

PyInterpreterState *interp = _PyInterpreterState_GET();
interp->anext_with_default = PyObject_GetAttrString(
mod, "_anext_with_default");
if (interp->anext_with_default == NULL) {
goto done;
}
rc = 0;

done:
Expand Down
40 changes: 39 additions & 1 deletion Python/clinic/bltinmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,7 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)

Py_CLEAR(interp->sysdict_copy);
Py_CLEAR(interp->builtins_copy);
Py_CLEAR(interp->anext_with_default);
Py_CLEAR(interp->dict);
#ifdef HAVE_FORK
Py_CLEAR(interp->before_forkers);
Expand Down
Loading