13
13
14
14
import _imp
15
15
import sys
16
+ import threading
16
17
import types
17
18
18
19
@@ -171,36 +172,54 @@ class _LazyModule(types.ModuleType):
171
172
172
173
def __getattribute__ (self , attr ):
173
174
"""Trigger the load of the module and return the attribute."""
174
- # All module metadata must be garnered from __spec__ in order to avoid
175
- # using mutated values.
176
- # Stop triggering this method.
177
- self .__class__ = types .ModuleType
178
- # Get the original name to make sure no object substitution occurred
179
- # in sys.modules.
180
- original_name = self .__spec__ .name
181
- # Figure out exactly what attributes were mutated between the creation
182
- # of the module and now.
183
- attrs_then = self .__spec__ .loader_state ['__dict__' ]
184
- attrs_now = self .__dict__
185
- attrs_updated = {}
186
- for key , value in attrs_now .items ():
187
- # Code that set the attribute may have kept a reference to the
188
- # assigned object, making identity more important than equality.
189
- if key not in attrs_then :
190
- attrs_updated [key ] = value
191
- elif id (attrs_now [key ]) != id (attrs_then [key ]):
192
- attrs_updated [key ] = value
193
- self .__spec__ .loader .exec_module (self )
194
- # If exec_module() was used directly there is no guarantee the module
195
- # object was put into sys.modules.
196
- if original_name in sys .modules :
197
- if id (self ) != id (sys .modules [original_name ]):
198
- raise ValueError (f"module object for { original_name !r} "
199
- "substituted in sys.modules during a lazy "
200
- "load" )
201
- # Update after loading since that's what would happen in an eager
202
- # loading situation.
203
- self .__dict__ .update (attrs_updated )
175
+ __spec__ = object .__getattribute__ (self , '__spec__' )
176
+ loader_state = __spec__ .loader_state
177
+ with loader_state ['lock' ]:
178
+ # Only the first thread to get the lock should trigger the load
179
+ # and reset the module's class. The rest can now getattr().
180
+ if object .__getattribute__ (self , '__class__' ) is _LazyModule :
181
+ # The first thread comes here multiple times as it descends the
182
+ # call stack. The first time, it sets is_loading and triggers
183
+ # exec_module(), which will access module.__dict__, module.__name__,
184
+ # and/or module.__spec__, reentering this method. These accesses
185
+ # need to be allowed to proceed without triggering the load again.
186
+ if loader_state ['is_loading' ] and attr .startswith ('__' ) and attr .endswith ('__' ):
187
+ return object .__getattribute__ (self , attr )
188
+ loader_state ['is_loading' ] = True
189
+
190
+ __dict__ = object .__getattribute__ (self , '__dict__' )
191
+
192
+ # All module metadata must be gathered from __spec__ in order to avoid
193
+ # using mutated values.
194
+ # Get the original name to make sure no object substitution occurred
195
+ # in sys.modules.
196
+ original_name = __spec__ .name
197
+ # Figure out exactly what attributes were mutated between the creation
198
+ # of the module and now.
199
+ attrs_then = loader_state ['__dict__' ]
200
+ attrs_now = __dict__
201
+ attrs_updated = {}
202
+ for key , value in attrs_now .items ():
203
+ # Code that set an attribute may have kept a reference to the
204
+ # assigned object, making identity more important than equality.
205
+ if key not in attrs_then :
206
+ attrs_updated [key ] = value
207
+ elif id (attrs_now [key ]) != id (attrs_then [key ]):
208
+ attrs_updated [key ] = value
209
+ __spec__ .loader .exec_module (self )
210
+ # If exec_module() was used directly there is no guarantee the module
211
+ # object was put into sys.modules.
212
+ if original_name in sys .modules :
213
+ if id (self ) != id (sys .modules [original_name ]):
214
+ raise ValueError (f"module object for { original_name !r} "
215
+ "substituted in sys.modules during a lazy "
216
+ "load" )
217
+ # Update after loading since that's what would happen in an eager
218
+ # loading situation.
219
+ __dict__ .update (attrs_updated )
220
+ # Finally, stop triggering this method.
221
+ self .__class__ = types .ModuleType
222
+
204
223
return getattr (self , attr )
205
224
206
225
def __delattr__ (self , attr ):
@@ -244,5 +263,7 @@ def exec_module(self, module):
244
263
loader_state = {}
245
264
loader_state ['__dict__' ] = module .__dict__ .copy ()
246
265
loader_state ['__class__' ] = module .__class__
266
+ loader_state ['lock' ] = threading .RLock ()
267
+ loader_state ['is_loading' ] = False
247
268
module .__spec__ .loader_state = loader_state
248
269
module .__class__ = _LazyModule
0 commit comments