8000 gh-117182: Allow lazily loaded modules to modify their own __class__ · python/cpython@19a2202 · GitHub
[go: up one dir, main page]

Skip to content

Commit 19a2202

Browse files
authored
gh-117182: Allow lazily loaded modules to modify their own __class__
1 parent ac45766 commit 19a2202

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

Lib/importlib/util.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,17 @@ def __getattribute__(self, attr):
178178
# Only the first thread to get the lock should trigger the load
179179
# and reset the module's class. The rest can now getattr().
180180
if object.__getattribute__(self, '__class__') is _LazyModule:
181+
__class__ = loader_state['__class__']
182+
181183
# Reentrant calls from the same thread must be allowed to proceed without
182184
# triggering the load again.
183185
# exec_module() and self-referential imports are the primary ways this can
184186
# happen, but in any case we must return something to avoid deadlock.
185187
if loader_state['is_loading']:
186-
return object.__getattribute__(self, attr)
188+
return __class__.__getattribute__(self, attr)
187189
loader_state['is_loading'] = True
188190

189-
__dict__ = object.__getattribute__(self, '__dict__')
191+
__dict__ = __class__.__getattribute__(self, '__dict__')
190192

191193
# All module metadata must be gathered from __spec__ in order to avoid
192194
# using mutated values.
@@ -216,8 +218,10 @@ def __getattribute__(self, attr):
216218
# Update after loading since that's what would happen in an eager
217219
# loading situation.
218220
__dict__.update(attrs_updated)
219-
# Finally, stop triggering this method.
220-
self.__class__ = types.ModuleType
221+
# Finally, stop triggering this method, if the module did not
222+
# already update its own __class__.
223+
if isinstance(self, _LazyModule):
224+
object.__setattr__(self, '__class__', __class__)
221225

222226
return getattr(self, attr)
223227

Lib/test/test_importlib/test_lazy.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,34 @@ def test_lazy_self_referential_modules(self):
196196
test_load = module.loads('{}')
197197
self.assertEqual(test_load, {})
198198

199+
def test_lazy_module_type_override(self):
200+
# Verify that lazy loading works with a module that modifies
201+
# its __class__ to be a custom type.
202+
203+
# Example module from PEP 726
204+
module = self.new_module(source_code="""\
205+
import sys
206+
from types import ModuleType
207+
208+
CONSTANT = 3.14
209+
210+
class ImmutableModule(ModuleType):
211+
def __setattr__(self, name, value):
212+
raise AttributeError('Read-only attribute!')
213+
214+
def __delattr__(self, name):
215+
raise AttributeError('Read-only attribute!')
216+
217+
sys.modules[__name__].__class__ = ImmutableModule
218+
""")
219+
sys.modules[TestingImporter.module_name] = module
220+
self.assertIsInstance(module, util._LazyModule)
221+
self.assertEqual(module.CONSTANT, 3.14)
222+
with self.assertRaises(AttributeError):
223+
module.CONSTANT = 2.71
224+
with self.assertRaises(AttributeError):
225+
del module.CONSTANT
226+
199227

200228
if __name__ == '__main__':
201229
unittest.main()
Lines changed: 2 additions & 0 deletions
36D0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Lazy-loading of modules that modify their own ``__class__`` no longer
2+
reverts the ``__class__`` to :class:`types.ModuleType`.

0 commit comments

Comments
 (0)
0