From cb46651406a4d7843cd0ac4b24079f589a4046f1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 2 Oct 2026 01:26:04 +0200 Subject: [PATCH 1/3] Don't reuse user-friendly name when discarding from sys.modules --- Objects/lazyimportobject.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Objects/lazyimportobject.c b/Objects/lazyimportobject.c index 69b3e1663d0786a..6f2c502396be209 100644 --- a/Objects/lazyimportobject.c +++ b/Objects/lazyimportobject.c @@ -225,6 +225,7 @@ lazy_import_path(PyLazyImportObject *m) return PyUnicode_Substring(m->lz_from, 0, dot); } +// A user-friendly name for use in repr and error messages static PyObject * lazy_import_name(PyLazyImportObject *m) { @@ -352,6 +353,25 @@ _PyLazyImport_IsResolving(PyThreadState *tstate, PyObject *op) return active; } +// Discard from sys.lazy_modules. +static int +lazy_import_discard(PyThreadState *tstate, PyLazyImportObject *m) { + PyObject *name = NULL; + if (PyLazyImport_CheckExact(m->lz_from)) { + name = lazy_import_path(m); + } + else { + name = Py_NewRef(m->lz_from); + } + if (name == NULL) { + return -1; + } + + int rc = _PyImport_DiscardLazyModule(tstate->interp, name); + Py_DECREF(name); + return rc; +} + static PyObject * lazy_import_resolve_impl(PyThreadState *tstate, PyObject *lazy_import, PyObject **imported_module) @@ -452,13 +472,8 @@ lazy_import_resolve_impl(PyThreadState *tstate, PyObject *lazy_import, lazy_import_add_exception_cause(tstate, lz); } assert(obj == NULL || !PyLazyImport_CheckExact(obj)); - if (obj != NULL) { - PyObject *name = lazy_import_name(lz); - if (name == NULL || - _PyImport_DiscardLazyModule(tstate->interp, name) < 0) { - Py_CLEAR(obj); - } - Py_XDECREF(name); + if (obj != NULL && (lazy_import_discard(tstate, lz) < 0)) { + Py_CLEAR(obj); } if (resolving != NULL) { // A failed set resize can leave the placeholder inserted. Removing by From 13a54cfc4c1df1533a24ac223d4876ed1636910b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 2 Oct 2026 01:31:34 +0200 Subject: [PATCH 2/3] Reuse return value from PySet_Discard --- Python/import.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/import.c b/Python/import.c index 54bf346c6f3557f..abf7854c712b037 100644 --- a/Python/import.c +++ b/Python/import.c @@ -287,7 +287,7 @@ _PyImport_ClearLazyModules(PyInterpreterState *interp) int _PyImport_DiscardLazyModule(PyInterpreterState *interp, PyObject *name) { - return PySet_Discard(LAZY_MODULES(interp), name) < 0 ? -1 : 0; + return PySet_Discard(LAZY_MODULES(interp), name); } static PyObject * From 9cbc64f7c0feddec95a9770aaff239df6ed9a728 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 2 Oct 2026 04:32:15 +0200 Subject: [PATCH 3/3] Share already-imported module between lazy_modules_add & all register_from_lazy_on_parent --- Python/import.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/Python/import.c b/Python/import.c index abf7854c712b037..823201d38b64ec6 100644 --- a/Python/import.c +++ b/Python/import.c @@ -4134,8 +4134,13 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals, return final_mod; } +// Add *name* to `sys.lazy_modules`. +// Skip the registration if *name* is already imported, and fully initialized. +// If *existing_mod_p* is not NULL, set it to the already imported object (or +// NULL if not found), for use by register_lazy_on_parent. static int -lazy_modules_add(PyThreadState *tstate, PyObject *name) +lazy_modules_add(PyThreadState *tstate, PyObject *name, + PyObject **existing_mod_p) { PyObject *mod = import_get_module(tstate, name); if (mod == NULL && PyErr_Occurred()) { @@ -4161,7 +4166,12 @@ lazy_modules_add(PyThreadState *tstate, PyObject *name) } Py_XDECREF(spec); } - Py_XDECREF(mod); + if (existing_mod_p != NULL) { + *existing_mod_p = mod; + } + else { + Py_XDECREF(mod); + } return loaded ? 0 : PySet_Add(LAZY_MODULES(tstate->interp), name); } @@ -4226,22 +4236,17 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name) } static int -register_from_lazy_on_parent(PyThreadState *tstate, PyObject *abs_name, - PyObject *from) +register_from_lazy_on_parent(PyThreadState *tstate, PyObject *existing_module, + PyObject *abs_name, PyObject *from) { // IMPORT_FROM returns stored attributes directly. Their imports are // already resolved or tracked by their own placeholders, so skip the alias. - PyObject *mod = import_get_module(tstate, abs_name); - if (mod == NULL && PyErr_Occurred()) { - return -1; - } int rc = 0; - if (mod != NULL && PyModule_Check(mod)) { - rc = PyDict_Contains(_PyModule_GetDict(mod), from); - } - Py_XDECREF(mod); - if (rc != 0) { - return rc < 0 ? -1 : 0; + if (existing_module != NULL && PyModule_Check(existing_module)) { + rc = PyDict_Contains(_PyModule_GetDict(existing_module), from); + if (rc != 0) { + return rc < 0 ? -1 : 0; + } } PyObject *fromname = PyUnicode_FromFormat("%U.%U", abs_name, from); @@ -4249,7 +4254,7 @@ register_from_lazy_on_parent(PyThreadState *tstate, PyObject *abs_name, return -1; } - rc = lazy_modules_add(tstate, fromname); + rc = lazy_modules_add(tstate, fromname, NULL); if (rc == 0) { rc = register_lazy_on_parent(tstate, fromname); } @@ -4341,6 +4346,8 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, } PyObject *res = NULL; + PyObject *existing_module = NULL; + if (fromlist != NULL && PyUnicode_Check(fromlist)) { fromlist = PyTuple_Pack(1, fromlist); if (fromlist == NULL) { @@ -4405,13 +4412,13 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, goto done; } - if (lazy_modules_add(tstate, abs_name) < 0) { + if (lazy_modules_add(tstate, abs_name, &existing_module) < 0) { goto error; } if (PyTuple_Check(fromlist) && PyTuple_GET_SIZE(fromlist)) { for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(fromlist); i++) { - if (register_from_lazy_on_parent(tstate, abs_name, + if (register_from_lazy_on_parent(tstate, existing_module, abs_name, PyTuple_GET_ITEM(fromlist, i)) < 0) { goto error; @@ -4428,6 +4435,7 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, done: Py_XDECREF(fromlist); Py_DECREF(abs_name); + Py_XDECREF(existing_module); return res; }