mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
Issue #17621: Introduce importlib.util.LazyLoader.
This commit is contained in:
parent
f22b2f0cf4
commit
a04dbe4fe7
5 changed files with 266 additions and 1 deletions
|
@ -1,5 +1,5 @@
|
|||
"""Utility code for constructing importers, etc."""
|
||||
|
||||
from . import abc
|
||||
from ._bootstrap import MAGIC_NUMBER
|
||||
from ._bootstrap import cache_from_source
|
||||
from ._bootstrap import decode_source
|
||||
|
@ -12,6 +12,7 @@ from ._bootstrap import _find_spec
|
|||
from contextlib import contextmanager
|
||||
import functools
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
|
||||
|
||||
|
@ -200,3 +201,94 @@ def module_for_loader(fxn):
|
|||
return fxn(self, module, *args, **kwargs)
|
||||
|
||||
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."""
|
||||
|
||||
def __getattribute__(self, attr):
|
||||
"""Trigger the load of the module and return the attribute."""
|
||||
# All module metadata must be garnered from __spec__ in order to avoid
|
||||
# using mutated values.
|
||||
# Stop triggering this method.
|
||||
self.__class__ = _Module
|
||||
# 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_now = self.__dict__
|
||||
attrs_updated = {}
|
||||
for key, value in attrs_now.items():
|
||||
# Code that set the attribute may have kept a reference to the
|
||||
# assigned object, making identity more important than equality.
|
||||
if key not in attrs_then:
|
||||
attrs_updated[key] = value
|
||||
elif id(attrs_now[key]) != id(attrs_then[key]):
|
||||
attrs_updated[key] = value
|
||||
self.__spec__.loader.exec_module(self)
|
||||
# If exec_module() was used directly there is no guarantee the module
|
||||
# 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))
|
||||
# Update after loading since that's what would happen in an eager
|
||||
# loading situation.
|
||||
self.__dict__.update(attrs_updated)
|
||||
return getattr(self, attr)
|
||||
|
||||
def __delattr__(self, attr):
|
||||
"""Trigger the load and then perform the deletion."""
|
||||
# To trigger the load and raise an exception if the attribute
|
||||
# doesn't exist.
|
||||
self.__getattribute__(attr)
|
||||
delattr(self, attr)
|
||||
|
||||
|
||||
class LazyLoader(abc.Loader):
|
||||
|
||||
"""A loader that creates a module which defers loading until attribute access."""
|
||||
|
||||
@staticmethod
|
||||
def __check_eager_loader(loader):
|
||||
if not hasattr(loader, 'exec_module'):
|
||||
raise TypeError('loader must define exec_module()')
|
||||
elif hasattr(loader.__class__, 'create_module'):
|
||||
if abc.Loader.create_module != loader.__class__.create_module:
|
||||
# Only care if create_module() is overridden in a subclass of
|
||||
# importlib.abc.Loader.
|
||||
raise TypeError('loader cannot define create_module()')
|
||||
|
||||
@classmethod
|
||||
def factory(cls, loader):
|
||||
"""Construct a callable which returns the eager loader made lazy."""
|
||||
cls.__check_eager_loader(loader)
|
||||
return lambda *args, **kwargs: cls(loader(*args, **kwargs))
|
||||
|
||||
def __init__(self, loader):
|
||||
self.__check_eager_loader(loader)
|
||||
self.loader = loader
|
||||
|
||||
def create_module(self, spec):
|
||||
"""Create a module which can have its __class__ manipulated."""
|
||||
return _Module(spec.name)
|
||||
|
||||
def exec_module(self, module):
|
||||
"""Make the module load lazily."""
|
||||
module.__spec__.loader = self.loader
|
||||
module.__loader__ = self.loader
|
||||
# Don't need to worry about deep-copying as trying to set an attribute
|
||||
# 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()
|
||||
module.__class__ = _LazyModule
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue