GeezerVison is showing github.com — Names that have been resolved are not consistently removed from sys.lazy_modules · Issue #155695 · python/cpython
Names that have been resolved are not consistently removed from sys.lazy_modules #155695Labels interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)release-blockertopic-lazy-importstype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error DescriptionBug report
Bug description:
I'm slightly hesitant to raise this because there's still discussion over what sys.lazy_modules should be. The documentation for sys.lazy_modules currently states:
When a lazily imported module is accessed for the first time, its name is removed from this set.
In at least two cases I'm aware of names are not being removed. Names may also not be modules, but I'd consider that a documentation issue while I believe the failure to remove names is a bug.
Names of modules that have previously been resolved will be re-added, but not removed when resolved again.
import sys
mods = sys.lazy_modules.copy()
def get_lazy():
return sorted(sys.lazy_modules - mods)
lazy import x # added
print(get_lazy())
x # removed
print(get_lazy())
lazy import x # added again
x # not removed
print(get_lazy())
Here I think the issue is adding x back to sys.lazy_modules when it's already in sys.modules.
Names that are not modules are not removed:
import sys
mods = sys.lazy_modules.copy()
def get_lazy():
return sorted(sys.lazy_modules - mods)
lazy from x import X
print(get_lazy())
X
print(get_lazy()) # Should be empty
CPython versions tested on:
3.15, CPython main branch
Operating systems tested on:
No response
Linked PRs
Activityprithviraj-chaudhuri commented on Aug 28, 2026 picnixz commented on Sep 2, 2026 prithviraj-chaudhuri commented on Sep 9, 2026 DavidCEllis commented on Sep 10, 2026 ContributorAuthor More actions I'm not working on a PR for this right now. As @picnixz said though, there's no clear answer on what to do though.
The documentation has been changed since I made this issue and now states:
When a lazily imported module is accessed for the first time, its name is typically removed from this set.
So technically it now matches the documentation but I don't think this is good behaviour that we should have. psyedgufran-svg commented on Sep 10, 2026 picnixz commented on Sep 10, 2026 picnixz commented on Sep 10, 2026 cc @DinoV @pablogsal for what they think we should do here (and whether we should do anything as well) Reacted by Bartosz Sławecki johnslavik commented on Sep 10, 2026 I see two ways to go here:
- Immediately return cached
sys.modules entry in lazy import x
- Clean up
sys.lazy_modules in cached paths (regular import or cached lazy import x + cached lazy from x import X)
For (1), plain lazy import x currently creates a proxy and adds x to sys.lazy_modules, even if the module is already cached. Accessing the proxy later calls the ordinary import machinery, whose cache lookup returns the existing module.
lazy from x import X already has an eager cache fast path, i.e. if x is cached in sys.modules and that dictionary contains X, it returns that attribute directly instead of creating an attribute proxy.
I prefer we check the cache in lazy import x and return early too, since lazy from x import X already does that.
For (2), cleanup currently happens when a module is loaded (not returned from cache). _find_and_load_unlocked() calls _imp._set_lazy_attributes() after loading, and that function discards the loaded module's name from sys.lazy_modules.
lazy from registers the parent module name and the qualified imported name, such as x.X before taking its eager attribute fast path, so consequently:
- If resolving
X loads x, cleanup removes x, but an ordinary attribute entry x.X remains.
- If
X is a submodule actually loaded as x.X, its module-loading path removes x.X.
- If
x and its attribute X are already available, the eager fast path returns X without removing either entry.
I believe we can fix both (1) and (2). Reacted by Petr Viktorin and David Ellis johnslavik commented on Sep 11, 2026 Judging by the fact that we already do cache lazy from x import X, I think it's correct to do the same in the simpler case, lazy import x. We should definitely clean up sys.lazy_modules in cached paths, so there is something to do regardless. I'll start making a patch, we can iterate. EAFP. Reacted by Brittany Reynoso brittanyrey commented on Sep 17, 2026 Hi, I'm working with @pablogsal to tie up loose ends around Lazy Imports ahead of the 3.15 final cut.
In the sake of time, I put together a PR to address both parts (1) and (2) of the issue. If you already have a change that you prefer--no hard feelings! That works too. johnslavik commented on Sep 17, 2026 @brittanyrey No worries at all, I hope I helped here. I can help reviewing, ping me anytime. Thanks for taking over and working on this. Reacted by Brittany Reynoso added a commit that references this issue on Oct 2, 2026gh-155695: Remove resolved names from sys.lazy_modules more consisten…
MetadataMetadatainterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)release-blockertopic-lazy-importstype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error StatusShow more project fields
DevelopmentNo branches or pull requestsIssue actions
|