diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index 81448e23787fb3..d1835fcf38c03e 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -41,7 +41,9 @@ PyAPI_FUNC(PyObject *) _PyImport_LoadLazyImportTstate( // new reference, NULL without an exception if absent, or NULL on error. // Set recheck_dict if a concurrent lookup may have already bound the child. extern PyObject * _PyImport_TryLoadLazySubmodule( - PyObject *module, PyObject *attr_name, int *recheck_dict); + PyObject *module, PyObject *attr_name, int suppress, int *recheck_dict); +extern int _PyImport_ClearLazySubmodule( + PyThreadState *tstate, PyObject *name, int bind); extern PyObject * _PyImport_LazyImportModuleLevelObject( PyThreadState *tstate, PyObject *name, PyObject *builtins, PyObject *globals, PyObject *locals, PyObject *fromlist, int level); diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index a9c61ea6db3a8c..fe501be1afc59b 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -356,9 +356,11 @@ struct _import_state { // lazily imported. This value is only for debugging/introspection // purposes and is not used by the runtime. PyObject *lazy_modules; - // A dict mapping package names to a set of submodule names that - // may need to be imported when an attribute is missing on the package. + // Package names map to pending children: declarations for plain imports, + // or None for from-import names that may be ordinary attributes. PyObject *lazy_pending_submodules; + // Avoid pending-child work for ordinary cached imports. + int has_lazy_submodules; #ifdef Py_GIL_DISABLED PyMutex lazy_mutex; #endif diff --git a/Include/internal/pycore_lazyimportobject.h b/Include/internal/pycore_lazyimportobject.h index c3f8417c089316..2da037b0b6fb84 100644 --- a/Include/internal/pycore_lazyimportobject.h +++ b/Include/internal/pycore_lazyimportobject.h @@ -18,6 +18,10 @@ PyAPI_FUNC(PyObject *) _PyLazyImport_New( struct _PyInterpreterFrame *frame, PyObject *builtins, PyObject *name, PyObject *fromlist); +extern PyObject *_PyLazyImport_LoadChild( + PyThreadState *tstate, PyObject *declaration, PyObject *name); +extern int _PyLazyImport_IsActive(PyObject *declaration); + extern int _PyLazyImport_IsResolving(PyThreadState *tstate, PyObject *op); // Resolve a placeholder and replace its binding if it is unchanged or holds diff --git a/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py index f9ef8ce2cfef8c..db065de83b2396 100644 --- a/Lib/test/test_lazy_import/__init__.py +++ b/Lib/test/test_lazy_import/__init__.py @@ -448,6 +448,119 @@ def test_lazy_with_from(self): self.assertNotIn("test.test_lazy_import.data.basic2", sys.modules) +@support.requires_subprocess() +class IndependentSubmoduleTests(LazyImportTestCase): + def check(self, code): + assert_python_ok('-c', textwrap.dedent(code)) + + def test_siblings(self): + for package, children in ( + ('test.test_lazy_import.data.pkg', ('b', 'bar')), + ('test.test_lazy_import.data.metasyntactic.foo', ('ack', 'bar')), + ): + for first, second in (children, children[::-1]): + with self.subTest(package=package, first=first): + self.check(f""" + import sys + lazy import {package}.{first} + lazy import {package}.{second} + assert {package}.{first} is sys.modules['{package}.{first}'] + assert '{package}.{second}' not in sys.modules + assert {package}.{second} is sys.modules['{package}.{second}'] + """) + + def test_star_import(self): + self.check(""" + lazy import urllib.nonexistent + lazy import urllib + assert urllib.__name__ == 'urllib' + from urllib import * + """) + + def test_deleted_child(self): + self.check(""" + lazy from xml import dom + assert dom.__name__ == 'xml.dom' + import xml + del xml.dom + assert not hasattr(xml, 'dom') + """) + + def test_cached_import_releases_builtins(self): + for statement in ('lazy from xml.dom import Node', + 'lazy import xml.dom.minidom'): + with self.subTest(statement=statement): + self.check(f""" + import builtins, gc, weakref + import xml.dom.minidom + class Payload: + pass + payload = Payload() + reference = weakref.ref(payload) + namespace = {{'__builtins__': dict(vars(builtins), payload=payload)}} + exec({statement!r}, namespace) + exec('Node' if 'from' in {statement!r} else 'xml.dom.minidom', namespace) + del namespace, payload + gc.collect() + assert reference() is None + """) + + def test_invalid_descendant_retries(self): + self.check(""" + lazy import test.test_lazy_import.data.pkg.b.foo + for _ in range(2): + try: + test.test_lazy_import.data.pkg.b.foo + except ModuleNotFoundError: + pass + else: + raise AssertionError('imported an attribute as a module') + import test.test_lazy_import.data.pkg.b + assert callable(test.test_lazy_import.data.pkg.b.foo) + """) + + def test_namespace_import_hooks(self): + self.check(""" + import builtins + first = {'__builtins__': vars(builtins).copy()} + second = {'__builtins__': vars(builtins).copy()} + exec('lazy import xml.dom', first) + exec('lazy import xml.dom', second) + def denied(*args): + raise AssertionError('used another namespace import hook') + second['__builtins__']['__import__'] = denied + exec('assert xml.dom.__name__ == "xml.dom"', first) + """) + + def test_retry_after_recursive_access(self): + self.check(""" + import sys, xml + from importlib.machinery import ModuleSpec + class Loader: + calls = 0 + def find_spec(self, name, path=None, target=None): + if name == 'xml.broken': + return ModuleSpec(name, self) + def create_module(self, spec): + return None + def exec_module(self, module): + self.calls += 1 + xml.broken + raise RuntimeError('failed initialization') + loader = Loader() + sys.meta_path.insert(0, loader) + lazy import xml.broken + for _ in range(2): + try: + xml.broken + except RuntimeError as exc: + assert str(exc) == 'failed initialization' + else: + raise AssertionError('returned a failed partial module') + assert loader.calls == 2 + """) + + class PackageTests(LazyImportTestCase): """Tests for lazy imports with packages.""" @@ -785,7 +898,7 @@ def test_chained_exception_import_shows_notes(self, name): lazy import {name} try: - _ = test + _ = {name} except ValueError as e: assert any( note.startswith("lazy import of '{name}' declared in ") diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-01-20-00-00.gh-issue-158140.small.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-01-20-00-00.gh-issue-158140.small.rst new file mode 100644 index 00000000000000..b9a0fc5e7a80c7 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-01-20-00-00.gh-issue-158140.small.rst @@ -0,0 +1,2 @@ +Resolve lazily imported sibling submodules independently, so accessing one +does not import another merely because its declaration came last. diff --git a/Objects/lazyimportobject.c b/Objects/lazyimportobject.c index e4a949a5456cfd..ce90f90973b155 100644 --- a/Objects/lazyimportobject.c +++ b/Objects/lazyimportobject.c @@ -9,6 +9,7 @@ #include "pycore_lazyimportobject.h" #include "pycore_long.h" #include "pycore_moduleobject.h" +#include "pycore_pyatomic_ft_wrappers.h" #include "pycore_pyerrors.h" #include "pycore_traceback.h" #include "pycore_tstate.h" @@ -25,6 +26,8 @@ typedef struct { // Declaration location. PyCodeObject *lz_code; int lz_instr_offset; + // The root was accessed, so its declared path requires import semantics. + int lz_active; } PyLazyImportObject; #define PyLazyImportObject_CAST(op) ((PyLazyImportObject *)(op)) @@ -70,6 +73,7 @@ _PyLazyImport_New(_PyInterpreterFrame *frame, PyObject *builtins, m->lz_code = NULL; m->lz_instr_offset = -1; + m->lz_active = 0; if (frame != NULL) { m->lz_code = (PyCodeObject *)Py_NewRef(_PyFrame_GetCode(frame)); @@ -429,10 +433,81 @@ lazy_import_resolve_impl(PyThreadState *tstate, PyObject *lazy_import, PyErr_SetString(PyExc_ImportError, "__import__ not found"); goto done; } + PyObject *name = Py_NewRef(root->lz_from); + if (first == NULL && + (fromlist == Py_None || + (PyTuple_Check(fromlist) && PyTuple_GET_SIZE(fromlist) == 0)) && + _PyImport_IsDefaultImportFunc(tstate->interp, import_func) && + root->lz_builtins == tstate->interp->builtins) { + // A plain import binds the root package. Its pending children are + // imported separately when their attributes are accessed. + Py_ssize_t dot = PyUnicode_FindChar( + name, '.', 0, PyUnicode_GET_LENGTH(name), 1); + if (dot >= 0) { + // Preserve eager traversal through aliased or non-package parents. + int regular = 1; + int complete = 1; + Py_ssize_t end = PyUnicode_GET_LENGTH(name); + for (Py_ssize_t i = dot; i >= 0 && regular; + i = PyUnicode_FindChar(name, '.', i + 1, end, 1)) { + PyObject *prefix = PyUnicode_Substring(name, 0, i); + PyObject *cached = prefix == NULL ? NULL : + lazy_import_get_loaded_attr(tstate, prefix, &_Py_ID(__name__)); + if (cached == NULL) { + complete = 0; + Py_XDECREF(prefix); + break; + } + PyObject *path = lazy_import_get_loaded_attr( + tstate, prefix, &_Py_ID(__path__)); + regular = path != NULL && PyUnicode_Check(cached) && + PyUnicode_Compare(cached, prefix) == 0; + Py_XDECREF(path); + Py_DECREF(cached); + Py_DECREF(prefix); + } + PyObject *loaded = lazy_import_get_loaded_attr( + tstate, name, &_Py_ID(__name__)); + if (regular && (!complete || loaded == NULL) && !PyErr_Occurred()) { + Py_SETREF(name, PyUnicode_Substring(name, 0, dot)); + } + Py_XDECREF(loaded); + if (PyErr_Occurred()) { + Py_CLEAR(name); + } + } + else if (dot == -2) { + Py_CLEAR(name); + } + if (name == NULL) { + goto done; + } + } obj = _PyEval_ImportNameWithImport( tstate, import_func, globals, globals, - root->lz_from, fromlist, _PyLong_GetZero() + name, fromlist, _PyLong_GetZero() ); + if (obj != NULL && name != root->lz_from) { + FT_ATOMIC_STORE_INT_RELAXED(root->lz_active, 1); + FT_ATOMIC_STORE_INT_RELAXED(tstate->interp->imports.has_lazy_submodules, 1); + int package = PyModule_CheckExact(obj) ? + PyDict_Contains(_PyModule_GetDict(obj), &_Py_ID(__path__)) : 0; + if (package < 0) { + Py_CLEAR(obj); + } + else if (!package) { + Py_DECREF(obj); + obj = _PyEval_ImportNameWithImport( + tstate, import_func, globals, globals, + root->lz_from, fromlist, _PyLong_GetZero()); + } + } + if (obj != NULL && name == root->lz_from && + _PyImport_IsDefaultImportFunc(tstate->interp, import_func) && + _PyImport_ClearLazySubmodule(tstate, name, 0) < 0) { + Py_CLEAR(obj); + } + Py_DECREF(name); obj = lazy_import_resolve_result(tstate, obj); // The normal importer may publish this module on its parent. Custom // hooks retain control of their own assignments to the parent. @@ -476,6 +551,56 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) return lazy_import_resolve_impl(tstate, lazy_import, NULL); } +// Resolve a pending child as an aliased import, preserving its declaration. +PyObject * +_PyLazyImport_LoadChild(PyThreadState *tstate, PyObject *declaration, + PyObject *name) +{ + PyLazyImportObject *source = (PyLazyImportObject *)declaration; + PyObject *lz = _PyLazyImport_New(NULL, source->lz_builtins, name, NULL); + Py_ssize_t end = PyUnicode_GET_LENGTH(name); + Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, end, 1); + if (dot == -2) { + Py_CLEAR(lz); + } + while (lz != NULL && dot >= 0) { + Py_ssize_t start = dot + 1; + dot = PyUnicode_FindChar(name, '.', start, end, 1); + if (dot == -2) { + Py_CLEAR(lz); + break; + } + PyObject *attr = PyUnicode_Substring(name, start, dot < 0 ? end : dot); + PyObject *next = attr == NULL ? NULL : + _PyLazyImport_New(NULL, NULL, lz, attr); + Py_XDECREF(attr); + Py_SETREF(lz, next); + } + if (lz == NULL) { + return NULL; + } + PyLazyImportObject *child = (PyLazyImportObject *)lz; + child->lz_code = (PyCodeObject *)Py_XNewRef(source->lz_code); + child->lz_instr_offset = source->lz_instr_offset; + PyObject *result = _PyImport_LoadLazyImportTstate(tstate, lz); + Py_DECREF(lz); + if (result != NULL && PyModule_Check(result) && + PyUnicode_Compare(source->lz_from, name) != 0 && + !PyDict_Contains(_PyModule_GetDict(result), &_Py_ID(__path__))) { + // Reaching a non-package must still reject an invalid dotted import. + Py_DECREF(result); + return _PyLazyImport_LoadChild(tstate, declaration, source->lz_from); + } + return result; +} + +int +_PyLazyImport_IsActive(PyObject *declaration) +{ + return FT_ATOMIC_LOAD_INT_RELAXED( + ((PyLazyImportObject *)declaration)->lz_active); +} + // Loading pkg.child can replace a placeholder in pkg.child with the module // before a from-import retrieves the value that belongs in that binding. // This is an optimization that can be safely skipped. diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 57df195b7f3201..d13b513ab468e4 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -1338,7 +1338,8 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress) } assert(m->md_dict != NULL); int recheck_dict; - attr = _PyImport_TryLoadLazySubmodule((PyObject *)m, name, &recheck_dict); + attr = _PyImport_TryLoadLazySubmodule((PyObject *)m, name, suppress, + &recheck_dict); if (attr != NULL || PyErr_Occurred()) { return attr; } diff --git a/Python/import.c b/Python/import.c index 576b8d47639fa4..dfff967c09736b 100644 --- a/Python/import.c +++ b/Python/import.c @@ -297,6 +297,7 @@ get_importtime_name(PyObject *name) return encoded; } +// Return whether the cached module was still initializing. static int import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *name) { @@ -342,7 +343,7 @@ import_ensure_initialized(PyInterpreterState *interp, PyObject *mod, PyObject *n #undef import_level } - return 0; + return rc; } static void remove_importlib_frames(PyThreadState *tstate); @@ -4013,7 +4014,8 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals, } if (mod != NULL && mod != Py_None) { - if (import_ensure_initialized(tstate->interp, mod, abs_name) < 0) { + int initializing = import_ensure_initialized(tstate->interp, mod, abs_name); + if (initializing < 0) { goto error; } /* Verify the module is still in sys.modules. Another thread may have @@ -4036,6 +4038,11 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals, } else { Py_DECREF(mod_check); + if (!initializing && + FT_ATOMIC_LOAD_INT_RELAXED(interp->imports.has_lazy_submodules) && + _PyImport_ClearLazySubmodule(tstate, abs_name, 1) < 0) { + goto error; + } } } else { @@ -4128,7 +4135,7 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals, return final_mod; } -// Ensure a set of pending submodule names exists for the parent. +// Ensure a dict of pending submodule names exists for the parent. // Returns a new reference. static PyObject * ensure_lazy_pending_submodules(PyDictObject *lazy_modules, PyObject *parent) @@ -4139,7 +4146,7 @@ ensure_lazy_pending_submodules(PyDictObject *lazy_modules, PyObject *parent) &lazy_submodules); if (err == 0) { // value isn't present - lazy_submodules = PySet_New(NULL); + lazy_submodules = PyDict_New(); if (lazy_submodules != NULL && _PyDict_SetItem_LockHeld(lazy_modules, parent, lazy_submodules) < 0) { @@ -4155,7 +4162,7 @@ ensure_lazy_pending_submodules(PyDictObject *lazy_modules, PyObject *parent) // is accessed, _Py_module_getattro_impl will check lazy_pending_submodules // and trigger the import. static int -register_lazy_on_parent(PyThreadState *tstate, PyObject *name) +register_lazy_on_parent(PyThreadState *tstate, PyObject *name, PyObject *source) { PyDictObject *pending = (PyDictObject *)LAZY_PENDING_SUBMODULES(tstate->interp); @@ -4178,7 +4185,18 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name) PyObject *submodules = ensure_lazy_pending_submodules(pending, parent); Py_DECREF(parent); - int err = submodules == NULL ? -1 : PySet_Add(submodules, child); + PyObject *fullname = PyUnicode_Substring(name, 0, end); + PyObject *cached = fullname == NULL ? NULL : + import_get_module(tstate, fullname); + Py_XDECREF(fullname); + PyObject *value = cached != NULL && cached != Py_None ? Py_None : source; + int err = -1; + if (submodules != NULL && !PyErr_Occurred()) { + err = source == Py_None ? + PyDict_SetDefaultRef(submodules, child, value, NULL) : + PyDict_SetItem(submodules, child, value); + } + Py_XDECREF(cached); Py_DECREF(child); Py_XDECREF(submodules); if (err < 0) { @@ -4201,15 +4219,92 @@ register_from_lazy_on_parent(PyThreadState *tstate, PyObject *abs_name, PyObject *lazy_modules = LAZY_MODULES(tstate->interp); int res = PySet_Add(lazy_modules, fromname); if (res == 0) { - res = register_lazy_on_parent(tstate, fromname); + res = register_lazy_on_parent(tstate, fromname, Py_None); } Py_DECREF(fromname); return res; } +// Release completed declarations, retaining names while their parent may fail. +// Cached eager imports also restore a child removed after failed validation. +int +_PyImport_ClearLazySubmodule(PyThreadState *tstate, PyObject *name, int bind) +{ + if (LAZY_PENDING_SUBMODULES(tstate->interp) == NULL || + PyDict_GET_SIZE(LAZY_PENDING_SUBMODULES(tstate->interp)) == 0) { + return 0; + } + Py_ssize_t end = PyUnicode_GET_LENGTH(name); + Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, end, -1); + if (dot < 0) { + return dot == -2 ? -1 : 0; + } + PyObject *parent = PyUnicode_Substring(name, 0, dot); + PyObject *child = PyUnicode_Substring(name, dot + 1, end); + PyObject *pending = NULL; + int rc = -1; + if (parent != NULL && child != NULL) { + rc = PyDict_GetItemRef(LAZY_PENDING_SUBMODULES(tstate->interp), + parent, &pending); + if (rc > 0) { + rc = PyDict_Contains(pending, child); + } + if (rc > 0) { + PyObject *loaded = import_get_module(tstate, name), *loaded_spec = NULL; + int loading = loaded == NULL ? (PyErr_Occurred() ? -1 : 0) : + PyObject_GetOptionalAttr(loaded, &_Py_ID(__spec__), &loaded_spec); + if (loading > 0) { + loading = _PyModuleSpec_IsInitializing(loaded_spec); + } + Py_XDECREF(loaded_spec); + Py_XDECREF(loaded); + if (loading != 0) { + if (PyErr_ExceptionMatches(PyExc_Exception)) { + PyErr_Clear(); + loading = 1; + } + rc = loading < 0 ? -1 : 0; + goto done; + } + PyObject *module = import_get_module(tstate, parent); + PyObject *spec = NULL; + int initializing = module == NULL ? (PyErr_Occurred() ? -1 : 0) : + PyObject_GetOptionalAttr(module, &_Py_ID(__spec__), &spec); + if (initializing > 0) { + initializing = _PyModuleSpec_IsInitializing(spec); + } + Py_XDECREF(spec); + if (bind && initializing == 0 && module != NULL && + PyModule_CheckExact(module) && PyDict_Contains(pending, child) > 0) { + PyObject *value = import_get_module(tstate, name); + if (value != NULL && value != Py_None) { + initializing = PyDict_SetDefaultRef( + _PyModule_GetDict(module), child, value, NULL) < 0 ? -1 : 0; + } + Py_XDECREF(value); + } + Py_XDECREF(module); + rc = initializing < 0 ? -1 : + (initializing ? PyDict_SetItem(pending, child, Py_None) : + PyDict_Pop(pending, child, NULL)); + } + } +done: + Py_XDECREF(pending); + Py_XDECREF(parent); + Py_XDECREF(child); + return rc < 0 ? -1 : 0; +} + +static int +lazy_submodule_matches(PyObject *value, void *expected) +{ + return value == expected; +} + PyObject * _PyImport_TryLoadLazySubmodule(PyObject *module, PyObject *attr_name, - int *recheck_dict) + int suppress, int *recheck_dict) { *recheck_dict = 0; PyObject *mod_dict = _PyModule_GetDict(module); @@ -4217,7 +4312,7 @@ _PyImport_TryLoadLazySubmodule(PyObject *module, PyObject *attr_name, if (PyDict_GetItemRef(mod_dict, &_Py_ID(__name__), &mod_name) <= 0) { return NULL; } - PyObject *mod = NULL, *pending_set = NULL; + PyObject *mod = NULL, *pending_set = NULL, *source = NULL; if (!PyUnicode_Check(mod_name)) { goto done; } @@ -4229,7 +4324,7 @@ _PyImport_TryLoadLazySubmodule(PyObject *module, PyObject *attr_name, } *recheck_dict = 1; - if (PySet_Contains(pending_set, attr_name) <= 0) { + if (PyDict_GetItemRef(pending_set, attr_name, &source) <= 0) { goto done; } PyObject *full_name = PyUnicode_FromFormat("%U.%U", mod_name, attr_name); @@ -4237,24 +4332,53 @@ _PyImport_TryLoadLazySubmodule(PyObject *module, PyObject *attr_name, goto done; } // Match eager from-imports: accept a partial module in an import cycle. - mod = PyImport_GetModule(full_name); - if ((mod == NULL && !PyErr_Occurred()) || mod == Py_None) { - Py_XDECREF(mod); - mod = import_find_and_load_with_name( - tstate, full_name, &_Py_ID(_find_and_load_lazy_submodule), Py_None); + if (source != Py_None && !suppress && + (_PyLazyImport_IsActive(source) || + !PyDict_Contains(mod_dict, &_Py_ID(__getattr__)))) { + mod = _PyLazyImport_LoadChild(tstate, source, full_name); + } + else { + mod = PyImport_GetModule(full_name); + if ((mod == NULL && !PyErr_Occurred()) || mod == Py_None) { + Py_XDECREF(mod); + mod = import_find_and_load_with_name( + tstate, full_name, &_Py_ID(_find_and_load_lazy_submodule), Py_None); + } } - Py_DECREF(full_name); if (mod == NULL) { + PyObject *exc = PyErr_GetRaisedException(); + PyObject *loaded = import_get_module(tstate, full_name); + if (loaded != NULL && loaded != Py_None) { + (void)_PyDict_DelItemIf(mod_dict, attr_name, + lazy_submodule_matches, loaded); + (void)PyDict_SetDefaultRef(pending_set, attr_name, source, NULL); + } + Py_XDECREF(loaded); + PyErr_SetRaisedException(exc); remove_importlib_frames(tstate); } /* Keep missing children pending: a finder may provide them later. */ - else if (mod == Py_None || - PyDict_SetItem(mod_dict, attr_name, mod) < 0 || - PySet_Discard(pending_set, attr_name) < 0) { - Py_CLEAR(mod); + else { + PyObject *spec = NULL; + int rc = mod == Py_None ? 0 : + PyObject_GetOptionalAttr(mod, &_Py_ID(__spec__), &spec); + int initializing = rc > 0 ? _PyModuleSpec_IsInitializing(spec) : rc; + Py_XDECREF(spec); + if (initializing < 0 && PyErr_ExceptionMatches(PyExc_Exception)) { + PyErr_Clear(); + initializing = 0; + } + if (mod == Py_None || initializing < 0 || + (initializing == 0 && + (PyDict_SetItem(mod_dict, attr_name, mod) < 0 || + PyDict_Pop(pending_set, attr_name, NULL) < 0))) { + Py_CLEAR(mod); + } } + Py_DECREF(full_name); done: + Py_XDECREF(source); Py_XDECREF(pending_set); Py_DECREF(mod_name); return mod; @@ -4370,7 +4494,7 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, } } } - else if (register_lazy_on_parent(tstate, abs_name) < 0) { + else if (register_lazy_on_parent(tstate, abs_name, res) < 0) { goto error; } @@ -5398,6 +5522,9 @@ _imp__set_lazy_attributes_impl(PyObject *module, PyObject *modobj, if (PySet_Discard(LAZY_MODULES(interp), name) < 0) { return NULL; } + if (_PyImport_ClearLazySubmodule(_PyThreadState_GET(), name, 0) < 0) { + return NULL; + } Py_RETURN_NONE; }