|
13 | 13 |
|
14 | 14 | import_imp |
15 | 15 | importsys |
| 16 | +importthreading |
16 | 17 | importtypes |
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 | | -forkey,valueinattrs_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 | | -ifkeynotinattrs_then: |
190 | | -attrs_updated[key]=value |
191 | | -elifid(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 | | -iforiginal_nameinsys.modules: |
197 | | -ifid(self)!=id(sys.modules[original_name]): |
198 | | -raiseValueError(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 | +withloader_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 | +ifobject.__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 | +ifloader_state['is_loading']andattr.startswith('__')andattr.endswith('__'): |
| 187 | +returnobject.__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 | +forkey,valueinattrs_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 | +ifkeynotinattrs_then: |
| 206 | +attrs_updated[key]=value |
| 207 | +elifid(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 | +iforiginal_nameinsys.modules: |
| 213 | +ifid(self)!=id(sys.modules[original_name]): |
| 214 | +raiseValueError(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 | returngetattr(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 |