Spaces:
Running
Running
# -*- 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 | |