Bug report
Bug description:
Discovered while working on #117178.
PEP 726 notes that the existing way for a module to override default methods is:
# mod.pyimportsysfromtypesimportModuleTypeCONSTANT=3.14classImmutableModule(ModuleType):
def__setattr__(name, value):
raiseAttributeError('Read-only attribute!')
def__delattr__(name):
raiseAttributeError('Read-only attribute!')
sys.modules[__name__].__class__=ImmutableModuleIf I want to load this module lazily, I can use this MRE (adapted from #117178):
importsysimportimportlibimportimportlib.utildeflazy_import(name):
spec=importlib.util.find_spec(name)
loader=importlib.util.LazyLoader(spec.loader)
spec.loader=loadermodule=importlib.util.module_from_spec(spec)
sys.modules[name] =moduleloader.exec_module(module)
returnmodule# Eagerly load the moduleeager_module=importlib.import_module(sys.argv[1])
print(f"Eagerly loaded: {type(eager_module)}")
delsys.modules[sys.argv[1]]
# Lazy-load the module...lazy_module=lazy_import(sys.argv[1])
print(f"Lazy-loaded: {type(lazy_module)}")
# ... and then trigger load by listing its contentsprint(dir(lazy_module)[:5])
print(f"Fully loaded: {type(lazy_module)}")This currently (with the fix in #117179) produces:
❯ ./python repro.py modEagerly loaded: <class 'mod.ImmutableModule'>Lazy-loaded: <class 'importlib.util._LazyModule'>Traceback (most recent call last): File "/home/chris/Projects/cpython/repro.py", line 23, in <module> print(dir(lazy_module)[:5]) ~~~^^^^^^^^^^^^^ File "/home/chris/Projects/cpython/Lib/importlib/util.py", line 223, in __getattribute__ self.__class__ = types.ModuleType ^^^^^^^^^^^^^^ File "/home/chris/Projects/cpython/mod.py", line 8, in __setattr__ raise AttributeError('Read-only attribute!')AttributeError: Read-only attribute!. Did you mean: '__doc__'?Making the following change:
- self.__class__ = types.ModuleType+ object.__setattr__(self, '__class__', types.ModuleType)
Results in:
❯ ./python repro.py modEagerly loaded: <class 'mod.ImmutableModule'>Lazy-loaded: <class 'importlib.util._LazyModule'>['CONSTANT', 'ImmutableModule', 'ModuleType', '__builtins__', '__cached__']Fully loaded: <class 'module'>
If instead we change to:
- self.__class__ = types.ModuleType+ if isinstance(self, _LazyModule):+ object.__setattr__(self, '__class__', types.ModuleType)
We get:
❯ ./python repro.py modEagerly loaded: <class 'mod.ImmutableModule'>Lazy-loaded: <class 'importlib.util._LazyModule'>['CONSTANT', 'ImmutableModule', 'ModuleType', '__builtins__', '__cached__']Fully loaded: <class 'mod.ImmutableModule'>
I believe making this change is the best way to respect the intentions of the module writer.
I've tagged this as a bug because these things have not worked together, but it might be more of a feature for them to start to, as neither is exactly standard practice.
CPython versions tested on:
3.11, 3.13, CPython main branch
Operating systems tested on:
Linux
Linked PRs
Bug report
Bug description:
Discovered while working on #117178.
PEP 726 notes that the existing way for a module to override default methods is:
If I want to load this module lazily, I can use this MRE (adapted from #117178):
This currently (with the fix in #117179) produces:
Making the following change:
Results in:
If instead we change to:
We get:
I believe making this change is the best way to respect the intentions of the module writer.
I've tagged this as a bug because these things have not worked together, but it might be more of a feature for them to start to, as neither is exactly standard practice.
CPython versions tested on:
3.11, 3.13, CPython main branch
Operating systems tested on:
Linux
Linked PRs