15
15
import _imp
16
16
import functools
17
17
import sys
18
+ import threading
18
19
import types
19
20
import warnings
20
21
@@ -225,36 +226,54 @@ class _LazyModule(types.ModuleType):
225
226
226
227
def __getattribute__ (self , attr ):
227
228
"""Trigger the load of the module and return the attribute."""
228
- # All module metadata must be garnered from __spec__ in order to avoid
229
- # using mutated values.
230
- # Stop triggering this method.
231
- self .__class__ = types .ModuleType
232
- # Get the original name to make sure no object substitution occurred
233
- # in sys.modules.
234
- original_name = self .__spec__ .name
235
- # Figure out exactly what attributes were mutated between the creation
236
- # of the module and now.
237
- attrs_then = self .__spec__ .loader_state ['__dict__' ]
238
- attrs_now = self .__dict__
239
- attrs_updated = {}
240
- for key , value in attrs_now .items ():
241
- # Code that set the attribute may have kept a reference to the
242
- # assigned object, making identity more important than equality.
243
- if key not in attrs_then :
244
- attrs_updated [key ] = value
245
- elif id (attrs_now [key ]) != id (attrs_then [key ]):
246
- attrs_updated [key ] = value
247
- self .__spec__ .loader .exec_module (self )
248
- # If exec_module() was used directly there is no guarantee the module
249
- # object was put into sys.modules.
250
- if original_name in sys .modules :
251
- if id (self ) != id (sys .modules [original_name ]):
252
- raise ValueError (f"module object for { original_name !r} "
253
- "substituted in sys.modules during a lazy "
254
- "load" )
255
- # Update after loading since that's what would happen in an eager
256
- # loading situation.
257
- self .__dict__ .update (attrs_updated )
229
+ __spec__ = object .__getattribute__ (self , '__spec__' )
230
+ loader_state = __spec__ .loader_state
231
+ with loader_state ['lock' ]:
232
+ # Only the first thread to get the lock should trigger the load
233
+ # and reset the module's class. The rest can now getattr().
234
+ if object .__getattribute__ (self , '__class__' ) is _LazyModule :
235
+ # The first thread comes here multiple times as it descends the
236
+ # call stack. The first time, it sets is_loading and triggers
237
+ # exec_module(), which will access module.__dict__, module.__name__,
238
+ # and/or module.__spec__, reentering this method. These accesses
239
+ # need to be allowed to proceed without triggering the load again.
240
+ if loader_state ['is_loading' ] and attr .startswith ('__' ) and attr .endswith ('__' ):
241
+ return object .__getattribute__ (self , attr )
242
+ loader_state ['is_loading' ] = True
243
+
244
+ __dict__ = object .__getattribute__ (self , '__dict__' )
245
+
246
+ # All module metadata must be gathered from __spec__ in order to avoid
247
+ # using mutated values.
248
+ # Get the original name to make sure no object substitution occurred
249
+ # in sys.modules.
250
+ original_name = __spec__ .name
251
+ # Figure out exactly what attributes were mutated between the creation
252
+ # of the module and now.
253
+ attrs_then = loader_state ['__dict__' ]
254
+ attrs_now = __dict__
255
+ attrs_updated = {}
256
+ for key , value in attrs_now .items ():
257
+ # Code that set an attribute may have kept a reference to the
258
+ # assigned object, making identity more important than equality.
259
+ if key not in attrs_then :
260
+ attrs_updated [key ] = value
261
+ elif id (attrs_now [key ]) != id (attrs_then [key ]):
262
+ attrs_updated [key ] = value
263
+ __spec__ .loader .exec_module (self )
264
+ # If exec_module() was used directly there is no guarantee the module
265
+ # object was put into sys.modules.
266
+ if original_name in sys .modules :
267
+ if id (self ) != id (sys .modules [original_name ]):
268
+ raise ValueError (f"module object for { original_name !r} "
269
+ "substituted in sys.modules during a lazy "
270
+ "load" )
271
+ # Update after loading since that's what would happen in an eager
272
+ # loading situation.
273
+ __dict__ .update (attrs_updated )
274
+ # Finally, stop triggering this method.
275
+ self .__class__ = types .ModuleType
276
+
258
277
return getattr (self , attr )
259
278
260
279
def __delattr__ (self , attr ):
@@ -298,5 +317,7 @@ def exec_module(self, module):
298
317
loader_state = {}
299
318
loader_state ['__dict__' ] = module .__dict__ .copy ()
300
319
loader_state ['__class__' ] = module .__class__
320
+ loader_state ['lock' ] = threading .RLock ()
321
+ loader_state ['is_loading' ] = False
301
322
module .__spec__ .loader_state = loader_state
302
323
module .__class__ = _LazyModule
0 commit comments