mirror of
https://github.com/python/cpython.git
synced 2025-11-01 14:11:41 +00:00
gh-117182: Allow lazily loaded modules to modify their own __class__
This commit is contained in:
parent
ac45766673
commit
19a2202067
3 changed files with 38 additions and 4 deletions
|
|
@ -178,15 +178,17 @@ def __getattribute__(self, attr):
|
||||||
# Only the first thread to get the lock should trigger the load
|
# Only the first thread to get the lock should trigger the load
|
||||||
# and reset the module's class. The rest can now getattr().
|
# and reset the module's class. The rest can now getattr().
|
||||||
if object.__getattribute__(self, '__class__') is _LazyModule:
|
if object.__getattribute__(self, '__class__') is _LazyModule:
|
||||||
|
__class__ = loader_state['__class__']
|
||||||
|
|
||||||
# Reentrant calls from the same thread must be allowed to proceed without
|
# Reentrant calls from the same thread must be allowed to proceed without
|
||||||
# triggering the load again.
|
# triggering the load again.
|
||||||
# exec_module() and self-referential imports are the primary ways this can
|
# exec_module() and self-referential imports are the primary ways this can
|
||||||
# happen, but in any case we must return something to avoid deadlock.
|
# happen, but in any case we must return something to avoid deadlock.
|
||||||
if loader_state['is_loading']:
|
if loader_state['is_loading']:
|
||||||
return object.__getattribute__(self, attr)
|
return __class__.__getattribute__(self, attr)
|
||||||
loader_state['is_loading'] = True
|
loader_state['is_loading'] = True
|
||||||
|
|
||||||
__dict__ = object.__getattribute__(self, '__dict__')
|
__dict__ = __class__.__getattribute__(self, '__dict__')
|
||||||
|
|
||||||
# All module metadata must be gathered from __spec__ in order to avoid
|
# All module metadata must be gathered from __spec__ in order to avoid
|
||||||
# using mutated values.
|
# using mutated values.
|
||||||
|
|
@ -216,8 +218,10 @@ def __getattribute__(self, attr):
|
||||||
# Update after loading since that's what would happen in an eager
|
# Update after loading since that's what would happen in an eager
|
||||||
# loading situation.
|
# loading situation.
|
||||||
__dict__.update(attrs_updated)
|
__dict__.update(attrs_updated)
|
||||||
# Finally, stop triggering this method.
|
# Finally, stop triggering this method, if the module did not
|
||||||
self.__class__ = types.ModuleType
|
# already update its own __class__.
|
||||||
|
if isinstance(self, _LazyModule):
|
||||||
|
object.__setattr__(self, '__class__', __class__)
|
||||||
|
|
||||||
return getattr(self, attr)
|
return getattr(self, attr)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,34 @@ def test_lazy_self_referential_modules(self):
|
||||||
test_load = module.loads('{}')
|
test_load = module.loads('{}')
|
||||||
self.assertEqual(test_load, {})
|
self.assertEqual(test_load, {})
|
||||||
|
|
||||||
|
def test_lazy_module_type_override(self):
|
||||||
|
# Verify that lazy loading works with a module that modifies
|
||||||
|
# its __class__ to be a custom type.
|
||||||
|
|
||||||
|
# Example module from PEP 726
|
||||||
|
module = self.new_module(source_code="""\
|
||||||
|
import sys
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
CONSTANT = 3.14
|
||||||
|
|
||||||
|
class ImmutableModule(ModuleType):
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
raise AttributeError('Read-only attribute!')
|
||||||
|
|
||||||
|
def __delattr__(self, name):
|
||||||
|
raise AttributeError('Read-only attribute!')
|
||||||
|
|
||||||
|
sys.modules[__name__].__class__ = ImmutableModule
|
||||||
|
""")
|
||||||
|
sys.modules[TestingImporter.module_name] = module
|
||||||
|
self.assertIsInstance(module, util._LazyModule)
|
||||||
|
self.assertEqual(module.CONSTANT, 3.14)
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
module.CONSTANT = 2.71
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
del module.CONSTANT
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Lazy-loading of modules that modify their own ``__class__`` no longer
|
||||||
|
reverts the ``__class__`` to :class:`types.ModuleType`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue