Issue #26186: Remove the restriction that built-in and extension

modules  can't be lazily loaded.

Thanks to Python 3.6 allowing for types.ModuleType to have its
__class__ mutated, the restriction can be lifted by calling
create_module() on the wrapped loader.
This commit is contained in:
Brett Cannon 2016-06-25 10:58:17 -07:00
parent da037616b1
commit 696c35e86b
4 changed files with 50 additions and 27 deletions

View file

@ -204,11 +204,6 @@ def module_for_loader(fxn):
return module_for_loader_wrapper
class _Module(types.ModuleType):
"""A subclass of the module type to allow __class__ manipulation."""
class _LazyModule(types.ModuleType):
"""A subclass of the module type which triggers loading upon attribute access."""
@ -218,13 +213,14 @@ class _LazyModule(types.ModuleType):
# All module metadata must be garnered from __spec__ in order to avoid
# using mutated values.
# Stop triggering this method.
self.__class__ = _Module
self.__class__ = types.ModuleType
# Get the original name to make sure no object substitution occurred
# in sys.modules.
original_name = self.__spec__.name
# Figure out exactly what attributes were mutated between the creation
# of the module and now.
attrs_then = self.__spec__.loader_state
attrs_then = self.__spec__.loader_state['__dict__']
original_type = self.__spec__.loader_state['__class__']
attrs_now = self.__dict__
attrs_updated = {}
for key, value in attrs_now.items():
@ -239,9 +235,9 @@ class _LazyModule(types.ModuleType):
# object was put into sys.modules.
if original_name in sys.modules:
if id(self) != id(sys.modules[original_name]):
msg = ('module object for {!r} substituted in sys.modules '
'during a lazy load')
raise ValueError(msg.format(original_name))
raise ValueError(f"module object for {original_name!r} "
"substituted in sys.modules during a lazy "
"load")
# Update after loading since that's what would happen in an eager
# loading situation.
self.__dict__.update(attrs_updated)
@ -275,8 +271,7 @@ class LazyLoader(abc.Loader):
self.loader = loader
def create_module(self, spec):
"""Create a module which can have its __class__ manipulated."""
return _Module(spec.name)
return self.loader.create_module(spec)
def exec_module(self, module):
"""Make the module load lazily."""
@ -286,5 +281,8 @@ class LazyLoader(abc.Loader):
# on an object would have triggered the load,
# e.g. ``module.__spec__.loader = None`` would trigger a load from
# trying to access module.__spec__.
module.__spec__.loader_state = module.__dict__.copy()
loader_state = {}
loader_state['__dict__'] = module.__dict__.copy()
loader_state['__class__'] = module.__class__
module.__spec__.loader_state = loader_state
module.__class__ = _LazyModule