diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index ab627c28c1fa5ee..bfc94e3e8529b75 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -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); diff --git a/Lib/_pybuiltins.py b/Lib/_pybuiltins.py new file mode 100644 index 000000000000000..01da295f53d0c73 --- /dev/null +++ b/Lib/_pybuiltins.py @@ -0,0 +1,42 @@ +"""Builtins implemented in Python. + +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 = 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) + + +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 diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index cdae58b3e89ae36..b5e0891feb794e8 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -1,7 +1,9 @@ import inspect +import traceback import types import unittest import contextlib +import warnings from test.support.import_helper import import_module from test.support import gc_collect, requires_working_socket @@ -709,7 +711,16 @@ def test_send(): async def test_throw(): p = ait_class() obj = anext(p, "completed") - self.assertRaises(SyntaxError, obj.throw, SyntaxError) + with warnings.catch_warnings(): + # Throwing into the unstarted anext() coroutine leaves the + # inner __anext__() awaitable never awaited. + warnings.simplefilter("ignore", RuntimeWarning) + self.assertRaises(SyntaxError, obj.throw, SyntaxError) + if isinstance(p, types.AsyncGeneratorType): + # The never-run asend() already registered the async + # generator with the loop's finalizer; close it explicitly + # so no aclose() task is left pending at loop close. + await p.aclose() return "completed" result = self.loop.run_until_complete(test_throw()) @@ -1036,6 +1047,40 @@ async def do_test(): result = self.loop.run_until_complete(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. + def filenames(exc): + return [frame.filename + for frame in traceback.extract_tb(exc.__traceback__)] + + class AIter: + def __aiter__(self): + return self + async def __anext__(self): + raise ZeroDivisionError + + # assertRaises() drops the traceback, so catch the exceptions here. + async def do_test(): + try: + anext(42, "default") + except TypeError as exc: + self.assertIn("", filenames(exc)) + else: + self.fail("TypeError was not raised") + + try: + await anext(AIter(), "default") + except ZeroDivisionError as exc: + self.assertIn("", filenames(exc)) + else: + self.fail("ZeroDivisionError was not raised") + return "completed" + + result = self.loop.run_until_complete(do_test()) + self.assertEqual(result, "completed") + def test_anext_iter(self): @types.coroutine def _async_yield(v): @@ -1132,9 +1177,13 @@ async def agenfn(): yield 'aaa' agen = agenfn() - with contextlib.closing(anext(agen, "default").__await__()) as g: - with self.assertRaises(MyError): - g.throw(MyError()) + with warnings.catch_warnings(): + # Throwing into the unstarted anext() coroutine leaves the + # inner asend() awaitable never awaited. + warnings.simplefilter("ignore", RuntimeWarning) + with contextlib.closing(anext(agen, "default").__await__()) as g: + with self.assertRaises(MyError): + g.throw(MyError()) def run_test(test): with self.subTest('pure-Python anext()'): diff --git a/Lib/test/test_asyncio/test_graph.py b/Lib/test/test_asyncio/test_graph.py index a442a346ff06d91..1326ef50c149b0b 100644 --- a/Lib/test/test_asyncio/test_graph.py +++ b/Lib/test/test_asyncio/test_graph.py @@ -148,6 +148,46 @@ async def main(): 'async generator CallStackTestBase.test_stack_async_gen..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', + [ + '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. diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index ab854d56d5a3ebf..c5900da09501b36 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -1312,8 +1312,12 @@ async def __anext__(self): def __aiter__(self): return self - with contextlib.closing(anext(A(), "a").__await__()) as anext_awaitable: - self.assertRaises(TypeError, anext_awaitable.close, 1) + with warnings.catch_warnings(): + # Closing the unstarted anext() coroutine leaves the inner + # __anext__() coroutine never awaited. + warnings.simplefilter("ignore", RuntimeWarning) + with contextlib.closing(anext(A(), "a").__await__()) as anext_awaitable: + self.assertRaises(TypeError, anext_awaitable.close, 1) def test_with_1(self): class Manager: diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py index 6399f952f9e912b..0cc0e651e45622b 100644 --- a/Lib/test/test_importlib/util.py +++ b/Lib/test/test_importlib/util.py @@ -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', + '_pybuiltins')) return {'Frozen': frozen, 'Source': source} diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index df5843abfcb8753..25276fc40cb0287 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -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 diff --git a/Makefile.pre.in b/Makefile.pre.in index 78a486623181fa8..b4131d1ee304d06 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -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) @@ -1664,6 +1665,7 @@ FROZEN_FILES_IN = \ Lib/importlib/_bootstrap.py \ Lib/importlib/_bootstrap_external.py \ Lib/zipimport.py \ + Lib/_pybuiltins.py \ Lib/abc.py \ Lib/codecs.py \ Lib/io.py \ @@ -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 \ @@ -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/_pybuiltins.py $(FREEZE_MODULE_BOOTSTRAP_DEPS) + $(FREEZE_MODULE_BOOTSTRAP) builtins $(srcdir)/Lib/_pybuiltins.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 diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-16-40-00.gh-issue-157361.anextpy.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-16-40-00.gh-issue-157361.anextpy.rst new file mode 100644 index 000000000000000..b56307056a7fff5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-16-40-00.gh-issue-157361.anextpy.rst @@ -0,0 +1,4 @@ +Implement :func:`anext` in Python instead of C, in a frozen ``_pybuiltins`` +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__`. diff --git a/Objects/iterobject.c b/Objects/iterobject.c index b5783c92c8eb689..2d5e3709a27dfb0 100644 --- a/Objects/iterobject.c +++ b/Objects/iterobject.c @@ -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) { @@ -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\ @@ -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 diff --git a/Objects/object.c b/Objects/object.c index a83f8d4c04ca079..e3f29b71301695e 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -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; @@ -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, diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 469fd77cc8be9dc..89fba29fd766677 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -306,6 +306,11 @@ $(IntDir)zipimport.g.h $(GeneratedFrozenModulesDir)Python\frozen_modules\zipimport.h + + builtins + $(IntDir)builtins.g.h + $(GeneratedFrozenModulesDir)Python\frozen_modules\builtins.h + abc $(IntDir)abc.g.h diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 976c99b7d24bdfd..ef2f2017a4519f3 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -549,6 +549,9 @@ Python Files + + Python Files + Python Files diff --git a/Programs/_bootstrap_python.c b/Programs/_bootstrap_python.c index 6443d814a22dabf..d30ef8c879d8153 100644 --- a/Programs/_bootstrap_python.c +++ b/Programs/_bootstrap_python.c @@ -13,6 +13,7 @@ #include "Python/frozen_modules/importlib._bootstrap.h" #include "Python/frozen_modules/importlib._bootstrap_external.h" #include "Python/frozen_modules/zipimport.h" +#include "Python/frozen_modules/builtins.h" /* End includes */ /* Note that a negative size indicates a package. */ @@ -21,6 +22,7 @@ static const struct _frozen bootstrap_modules[] = { {"_frozen_importlib", _Py_M__importlib__bootstrap, (int)sizeof(_Py_M__importlib__bootstrap)}, {"_frozen_importlib_external", _Py_M__importlib__bootstrap_external, (int)sizeof(_Py_M__importlib__bootstrap_external)}, {"zipimport", _Py_M__zipimport, (int)sizeof(_Py_M__zipimport)}, + {"_pybuiltins", _Py_M__builtins, (int)sizeof(_Py_M__builtins)}, {0, 0, 0} /* bootstrap sentinel */ }; static const struct _frozen stdlib_modules[] = { @@ -36,6 +38,7 @@ const struct _frozen *_PyImport_FrozenTest = test_modules; static const struct _module_alias aliases[] = { {"_frozen_importlib", "importlib._bootstrap"}, {"_frozen_importlib_external", "importlib._bootstrap_external"}, + {"_pybuiltins", "builtins"}, {0, 0} /* aliases sentinel */ }; const struct _module_alias *_PyImport_FrozenAliases = aliases; diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index d28e6fa9cd01aed..965cf20fe617787 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1960,52 +1960,6 @@ builtin_aiter_impl(PyObject *module, PyObject *object, PyObject *stop_value, return _PyACallIter_New(object, stop_value, stop_exception); } -PyObject *PyAnextAwaitable_New(PyObject *, PyObject *); - -/*[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; - PyObject *awaitable; - - 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; - } - - awaitable = (*t->tp_as_async->am_anext)(aiterator); - if (awaitable == NULL) { - return NULL; - } - if (default_value == NULL) { - return awaitable; - } - - PyObject* new_awaitable = PyAnextAwaitable_New( - awaitable, default_value); - Py_DECREF(awaitable); - return new_awaitable; -} - - /*[clinic input] len as builtin_len @@ -3500,7 +3454,6 @@ 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 @@ -3539,6 +3492,57 @@ static struct PyModuleDef builtinsmodule = { }; +/* Builtins implemented in Python. + + 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. */ + +int +_PyBuiltin_InitPythonFunctions(PyObject *dict) +{ + if (PyImport_ImportFrozenModule("_pybuiltins") <= 0) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_ImportError, + "frozen module _pybuiltins not found"); + } + return -1; + } + PyObject *mod = PyImport_AddModuleRef("_pybuiltins"); + if (mod == NULL) { + return -1; + } + + int rc = -1; + PyObject *all = PyObject_GetAttr(mod, &_Py_ID(__all__)); + if (all == NULL) { + goto done; + } + Py_ssize_t n = PyList_Size(all); + if (n < 0) { + goto done; + } + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *name = PyList_GET_ITEM(all, i); + PyObject *func = PyObject_GetAttr(mod, name); + if (func == NULL) { + goto done; + } + int r = PyDict_SetItem(dict, name, func); + Py_DECREF(func); + if (r < 0) { + goto done; + } + } + rc = 0; + +done: + Py_XDECREF(all); + Py_DECREF(mod); + return rc; +} + PyObject * _PyBuiltin_Init(PyInterpreterState *interp) { diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index c10bb03d8178161..5858ca9ff88ec22 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -1011,44 +1011,6 @@ builtin_aiter(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec return return_value; } -PyDoc_STRVAR(builtin_anext__doc__, -"anext($module, async_iterator, default=, /)\n" -"--\n" -"\n" -"Return the next item from the async iterator.\n" -"\n" -"If default is given and the async iterator is exhausted,\n" -"it is returned instead of raising StopAsyncIteration."); - -#define BUILTIN_ANEXT_METHODDEF \ - {"anext", _PyCFunction_CAST(builtin_anext), METH_FASTCALL, builtin_anext__doc__}, - -static PyObject * -builtin_anext_impl(PyObject *module, PyObject *aiterator, - PyObject *default_value); - -static PyObject * -builtin_anext(PyObject *module, PyObject *const *args, Py_ssize_t nargs) -{ - PyObject *return_value = NULL; - PyObject *aiterator; - PyObject *default_value = NULL; - - if (!_PyArg_CheckPositional("anext", nargs, 1, 2)) { - goto exit; - } - aiterator = args[0]; - if (nargs < 2) { - goto skip_optional; - } - default_value = args[1]; -skip_optional: - return_value = builtin_anext_impl(module, aiterator, default_value); - -exit: - return return_value; -} - PyDoc_STRVAR(builtin_len__doc__, "len($module, obj, /)\n" "--\n" @@ -1539,4 +1501,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=5fb1ac6a4253ee2f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b56739f2e13f616a input=a9049054013a1b77]*/ diff --git a/Python/frozen.c b/Python/frozen.c index 9433d90c15e2eca..1f92ea01dc38bbc 100644 --- a/Python/frozen.c +++ b/Python/frozen.c @@ -44,6 +44,7 @@ #include "frozen_modules/importlib._bootstrap.h" #include "frozen_modules/importlib._bootstrap_external.h" #include "frozen_modules/zipimport.h" +#include "frozen_modules/builtins.h" #include "frozen_modules/abc.h" #include "frozen_modules/codecs.h" #include "frozen_modules/io.h" @@ -71,6 +72,7 @@ static const struct _frozen bootstrap_modules[] = { {"_frozen_importlib", _Py_M__importlib__bootstrap, (int)sizeof(_Py_M__importlib__bootstrap), false}, {"_frozen_importlib_external", _Py_M__importlib__bootstrap_external, (int)sizeof(_Py_M__importlib__bootstrap_external), false}, {"zipimport", _Py_M__zipimport, (int)sizeof(_Py_M__zipimport), false}, + {"_pybuiltins", _Py_M__builtins, (int)sizeof(_Py_M__builtins), false}, {0, 0, 0} /* bootstrap sentinel */ }; static const struct _frozen stdlib_modules[] = { @@ -119,6 +121,7 @@ const struct _frozen *_PyImport_FrozenTest = test_modules; static const struct _module_alias aliases[] = { {"_frozen_importlib", "importlib._bootstrap"}, {"_frozen_importlib_external", "importlib._bootstrap_external"}, + {"_pybuiltins", "builtins"}, {"__hello_alias__", "__hello__"}, {"__phello_alias__", "__hello__"}, {"__phello_alias__.spam", "__hello__"}, diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 500a1a1949a5a8a..3feb06915a59c2c 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -924,6 +924,16 @@ pycore_init_builtins(PyThreadState *tstate) return _PyStatus_ERR("failed to add exceptions to builtins"); } + /* The Python-implemented builtins live in the frozen _pybuiltins module. + Programs/_freeze_module has no frozen modules (it's what creates + them) and opts out via _install_importlib, like the import system. */ + const PyConfig *config = _PyInterpreterState_GetConfig(interp); + if (config->_install_importlib) { + if (_PyBuiltin_InitPythonFunctions(builtins_dict) < 0) { + return _PyStatus_ERR("failed to add Python-implemented builtins"); + } + } + interp->builtins_copy = PyDict_Copy(interp->builtins); if (interp->builtins_copy == NULL) { goto error; diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h index 8937e666bbbdd5b..565be27b7cebda5 100644 --- a/Python/stdlib_module_names.h +++ b/Python/stdlib_module_names.h @@ -65,6 +65,7 @@ static const char* _Py_stdlib_module_names[] = { "_posixsubprocess", "_py_abc", "_py_warnings", +"_pybuiltins", "_pydatetime", "_pydecimal", "_pyio", diff --git a/Tools/build/freeze_modules.py b/Tools/build/freeze_modules.py index a866336fa78879e..8c817daec0f0b37 100644 --- a/Tools/build/freeze_modules.py +++ b/Tools/build/freeze_modules.py @@ -16,6 +16,9 @@ FROZEN_ONLY = os.path.join(ROOT_DIR, 'Tools', 'freeze', 'flag.py') STDLIB_DIR = os.path.join(ROOT_DIR, 'Lib') +# Frozen under the "builtins" ID rather than its own name, so that the frames +# of the builtins it defines show up as "" in tracebacks. +PYBUILTINS = os.path.join(STDLIB_DIR, '_pybuiltins.py') # If FROZEN_MODULES_DIR or DEEPFROZEN_MODULES_DIR is changed then the # .gitattributes and .gitignore files needs to be updated. FROZEN_MODULES_DIR = os.path.join(ROOT_DIR, 'Python', 'frozen_modules') @@ -45,6 +48,8 @@ # This module is important because some Python builds rely # on a builtin zip file instead of a filesystem. 'zipimport', + # Builtins implemented in Python; loaded while builtins is set up. + f'builtins : _pybuiltins = {PYBUILTINS}', ]), # (You can delete entries from here down to the end of the list.) ('stdlib - startup, without site (python -S)', [ @@ -91,6 +96,7 @@ 'importlib._bootstrap', 'importlib._bootstrap_external', 'zipimport', + 'builtins', } diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index b8488899c4595de..67ced170243e4a2 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -60,7 +60,6 @@ Objects/iterobject.c - PyCallIter_Type - Objects/iterobject.c - PySeqIter_Type - Objects/iterobject.c - _PyACallIter_Type - Objects/iterobject.c - _PyACallIterAwaitable_Type - -Objects/iterobject.c - _PyAnextAwaitable_Type - Objects/lazyimportobject.c - PyLazyImport_Type - Objects/listobject.c - PyListIter_Type - Objects/listobject.c - PyListRevIter_Type - @@ -77,7 +76,6 @@ Objects/object.c - _PyNone_Type - Objects/object.c - _PyNotImplemented_Type - Objects/object.c - _PyACallIter_Type - Objects/object.c - _PyACallIterAwaitable_Type - -Objects/object.c - _PyAnextAwaitable_Type - Objects/odictobject.c - PyODictItems_Type - Objects/odictobject.c - PyODictIter_Type - Objects/odictobject.c - PyODictKeys_Type -