|
15 | 15 | import_imp |
16 | 16 | importfunctools |
17 | 17 | importsys |
| 18 | +importthreading |
18 | 19 | importtypes |
19 | 20 | importwarnings |
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 | | -forkey,valueinattrs_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 | | -ifkeynotinattrs_then: |
244 | | -attrs_updated[key]=value |
245 | | -elifid(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 | | -iforiginal_nameinsys.modules: |
251 | | -ifid(self)!=id(sys.modules[original_name]): |
252 | | -raiseValueError(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 | +withloader_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 | +ifobject.__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 | +ifloader_state['is_loading']andattr.startswith('__')andattr.endswith('__'): |
| 241 | +returnobject.__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 | +forkey,valueinattrs_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 | +ifkeynotinattrs_then: |
| 260 | +attrs_updated[key]=value |
| 261 | +elifid(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 | +iforiginal_nameinsys.modules: |
| 267 | +ifid(self)!=id(sys.modules[original_name]): |
| 268 | +raiseValueError(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 | returngetattr(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 |