Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| # Copyright (c) XiMing Xing. All rights reserved. | |
| # Author: XiMing Xing | |
| # Description: | |
| import importlib | |
| import importlib.util | |
| import os | |
| import sys | |
| def attach(package_name, submodules=None, submod_attrs=None): | |
| """Attach lazily loaded submodules, functions, or other attributes. | |
| Typically, modules import submodules and attributes as follows:: | |
| import mysubmodule | |
| import anothersubmodule | |
| from .foo import someattr | |
| The idea is to replace a package's `__getattr__`, `__dir__`, and | |
| `__all__`, such that all imports work exactly the way they did | |
| before, except that they are only imported when used. | |
| The typical way to call this function, replacing the above imports, is:: | |
| __getattr__, __lazy_dir__, __all__ = lazy.attach( | |
| __name__, | |
| ['mysubmodule', 'anothersubmodule'], | |
| {'foo': 'someattr'} | |
| ) | |
| This functionality requires Python 3.7 or higher. | |
| Parameters | |
| ---------- | |
| package_name : str | |
| Typically use ``__name__``. | |
| submodules : set | |
| List of submodules to attach. | |
| submod_attrs : dict | |
| Dictionary of submodule -> list of attributes / functions. | |
| These attributes are imported as they are used. | |
| Returns | |
| ------- | |
| __getattr__, __dir__, __all__ | |
| """ | |
| if submod_attrs is None: | |
| submod_attrs = {} | |
| if submodules is None: | |
| submodules = set() | |
| else: | |
| submodules = set(submodules) | |
| attr_to_modules = { | |
| attr: mod for mod, attrs in submod_attrs.items() for attr in attrs | |
| } | |
| __all__ = list(submodules | attr_to_modules.keys()) | |
| def __getattr__(name): | |
| if name in submodules: | |
| return importlib.import_module(f'{package_name}.{name}') | |
| elif name in attr_to_modules: | |
| submod = importlib.import_module( | |
| f'{package_name}.{attr_to_modules[name]}' | |
| ) | |
| return getattr(submod, name) | |
| else: | |
| raise AttributeError(f'No {package_name} attribute {name}') | |
| def __dir__(): | |
| return __all__ | |
| eager_import = os.environ.get('EAGER_IMPORT', '') | |
| if eager_import not in ['', '0', 'false']: | |
| for attr in set(attr_to_modules.keys()) | submodules: | |
| __getattr__(attr) | |
| return __getattr__, __dir__, list(__all__) | |
| def load(fullname): | |
| """Return a lazily imported proxy for a module. | |
| We often see the following pattern:: | |
| def myfunc(): | |
| import scipy as sp | |
| sp.argmin(...) | |
| .... | |
| This is to prevent a module, in this case `scipy`, from being | |
| imported at function definition time, since that can be slow. | |
| This function provides a proxy module that, upon access, imports | |
| the actual module. So the idiom equivalent to the above example is:: | |
| sp = lazy.load("scipy") | |
| def myfunc(): | |
| sp.argmin(...) | |
| .... | |
| The initial import time is fast because the actual import is delayed | |
| until the first attribute is requested. The overall import time may | |
| decrease as well for users that don't make use of large portions | |
| of the library. | |
| Parameters | |
| ---------- | |
| fullname : str | |
| The full name of the module or submodule to import. For example:: | |
| sp = lazy.load('scipy') # import scipy as sp | |
| spla = lazy.load('scipy.linalg') # import scipy.linalg as spla | |
| Returns | |
| ------- | |
| pm : importlib.util._LazyModule | |
| Proxy module. Can be used like any regularly imported module. | |
| Actual loading of the module occurs upon first attribute request. | |
| """ | |
| try: | |
| return sys.modules[fullname] | |
| except KeyError: | |
| pass | |
| spec = importlib.util.find_spec(fullname) | |
| if spec is None: | |
| raise ModuleNotFoundError(f"No module name '{fullname}'") | |
| module = importlib.util.module_from_spec(spec) | |
| sys.modules[fullname] = module | |
| loader = importlib.util.LazyLoader(spec.loader) | |
| loader.exec_module(module) | |
| return module | |