Python中的惰性导入已经讨论了很长时间,并且已经提出了一些建议(例如PEP609 - Lazy Imports),使其成为未来的内置(可选)功能。
我正在开发一个CLI包,所以启动时间非常重要,我想通过延迟加载我正在使用的一些模块来加快它。
到目前为止
通过修改函数以实现从Python的importlib documentation的延迟导入,我构建了以下LazyImport
类:
import importlib.util
import sys
from types import ModuleType
class LazyImport:
def __init__(self):
pass
def __new__(
cls,
name: str,
) -> type(ModuleType):
try:
return sys.modules[name]
except KeyError:
spec = importlib.util.find_spec(name)
if spec:
loader = importlib.util.LazyLoader(spec.loader)
spec.loader = loader
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
loader.exec_module(module)
return module
else:
raise ModuleNotFoundError(f"No module named '{name}'") from None
- 注:* 这是我能想到的将函数转换为类的最佳方法,但如果你有更好的方法,我也欢迎你对此提出反馈。
这对于顶级模块导入来说很好:
而不是导入(例如)xarray
作为
import xarray as xr
我会跑
xr = LazyImport('xarray')
一切都按预期工作,不同的是xarray
模块被添加到sys.modules
,但它还没有加载到内存中(模块脚本还没有运行)。
只有当变量xr
第一次被引用时(例如通过调用方法/子模块或简单地引用它),模块才会被加载到内存中(因此模块脚本运行)。因此,对于上面的示例,这些语句中的任何一个都将把xarray
模块加载到内存中:
xr.DataArray([1,2,3])
print(xr)
xr
我想要的
现在,我希望能够实现相同的结果,但当我从模块中加载类,函数或变量时。
因此(例如)不是通过以下方式导入xarray.DataArray
类:
from xarray import DataArray as Da
我想要的是:
Da = LazyImport('DataArray', _from='xarray')
这样xarray
模块就被添加到sys.modules
中,但还没有加载到内存中,只有当我第一次引用Da
变量时才会被加载。Da
变量将引用xarray
模块的DataArray
类。
我努力
我尝试了一些选项,例如
xr = LazyImport('xarray')
Da = getattr(xr, 'DataArray')
或者通过修改LazyImport
类,但是每次我引用xr
时,xarray
模块都会加载到内存中。我无法在内存中不加载xarray
的情况下创建Da
变量。
参考示例,我需要的基本上是Da
变量的延迟求值,仅当我第一次引用Da
时才求值(xarray
模块的DataArray
类)(因此仅在此时运行模块脚本)。
另外,我不希望在要计算的变量Da
上调用任何方法(例如Da.load()
),但我希望在第一次引用时直接计算变量。
我查看了一些外部库(比如lazy_loader),但没有发现一个允许从外部模块(除了您正在开发的模块)延迟导入类和变量的库。
有谁知道从模块中实现延迟导入的解决方案吗?
2条答案
按热度按时间3b6akqbq1#
你可以让你的lazy对象充当
.
(getattr
)和()
(call
)操作的代理:但这是相当脆弱的-如果有一天你决定懒惰地导入一个常数呢?还是传递“导入”变量?您最初的方法要好得多,只需坚持使用
xr.DataArray
即可。9avjhtql2#
这个答案可能不是很令人满意,但我认为我已经尽可能接近于任意对象的延迟加载。
可悲的是,我想这是你最接近你想要达到的目标了。我们不能使用
LazyLoader
对不可变类型使用的guts-scooping方法。我有另一个想法,我想追求,但我不相信它会导致任何地方。添加
所以,事实证明你可以更进一步,通过编写这样的函数:
这给了你一个函数
replace
,它可以用来替换引用。注意事项:1.这使用了一个用于调试的函数。来自文档:
警告:使用
get_referrers()
返回的对象时必须小心,因为其中一些对象可能仍在构造中,因此处于暂时无效的状态。避免将get_referrers()
用于除调试以外的任何目的。1.它目前只支持列表、字典和带有
__dict__
属性的对象(包括模块和类)。1.由于显而易见的原因,直接存储在元组和其他不可变类中的值不能被替换。
gc.get_referrers
找不到局部变量。1.当替换字典中用作键的对象时,它会改变顺序。这可以以降低运行时性能为代价来修复(将替换键之后的所有项移到后面)。
如果您可以接受这些警告,则可以将
LazyAttribute.__getattribute__
更改为:.并且延迟加载的对象将在任何可以替换的地方被替换。这实际上取代了它们,而不仅仅是代理它们或做
LazyLoader
做的事情。