Skip to content

gh-151408: Fix bug with lazy importing a previously imported and removed module#151412

Draft
brittanyrey wants to merge 5 commits into
python:mainfrom
brittanyrey:bugfix2-lazy-imports-submod
Draft

gh-151408: Fix bug with lazy importing a previously imported and removed module#151412
brittanyrey wants to merge 5 commits into
python:mainfrom
brittanyrey:bugfix2-lazy-imports-submod

Conversation

@brittanyrey

@brittanyrey brittanyrey commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Fix
This PR ensures that a submodule is fully re-imported instead of relying on pre-existing cached submodule objects.

Testing
This PR adds several tests for TDD that fail prior to the ceval.c changes.
Also, after this PR lands-- test_trace no longer fails with lazy_imports=all as documented in the attached issue.

#151408

Comment thread Python/import.c
submod = import_get_module(tstate, fullmodname);
}
Py_DECREF(fullmodname);
if (submod != NULL) {

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.

sys.modules[fullmodname] can be None. This returns it as the binding instead of raising ModuleNotFoundError; we need to handle the sentinel explicitly.

Comment thread Python/import.c
Py_DECREF(attr);
}
else {
obj = _PyEval_ImportFrom(tstate, from, lz->lz_attr);

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.

Small nit: on the not-found path we look up lz->lz_attr here and then _PyEval_ImportFrom looks it up again, so a package __getattr__ runs twice, no? Not a blocker, but worth a comment if we keep it.

Comment thread Python/import.c
return Py_NewRef(attr);
}

int is_package = PyObject_HasAttrWithError(package, &_Py_ID(__path__));

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.

Nit: do we have a test for the non-package branch? We dropped test_from_import_module_attr_from_non_package in the last commit, so the is_package <= 0 and matches <= 0 paths here look uncovered now. Maybe add a small lazy from test where the parent is a plain module exposing a module-valued attribute.

Comment thread Python/ceval.c
return NULL;
}
if (ret != NULL) {
PyObject *resolved = _PyImport_ResolveLazyImportFromAttr(

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.

This re-import runs while executing the lazy from statement, before bar is accessed. We should keep a lazy proxy here and repair it during reification.

Comment thread Python/import.c

PyObject *submod = import_get_module(tstate, fullmodname);
if (submod == NULL && !_PyErr_Occurred(tstate)) {
PyObject *imported = PyImport_ImportModuleLevelObject(

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.

This bypasses the __import__ captured by the lazy object, so custom import hooks are ignored. We need to pass the captured hook through this path.

Comment thread Python/import.c
return NULL;
}

int matches = (attr_name != NULL && PyUnicode_Check(attr_name))

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.

__name__ == fullmodname does not prove this is an importable submodule. Packages can export synthetic modules, so this turns a valid from-import into ModuleNotFoundError.

Comment thread Python/import.c
return matches < 0 ? NULL : Py_NewRef(attr);
}

PyObject *submod = import_get_module(tstate, fullmodname);

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.

This can return a module that is still initializing in another thread. We need the initialization wait and recheck from PyImport_GetModule().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants