我写了一个粗略的惰性导入器,所以你可以做这样的事情:
from loader import Lazy
httpx = Lazy("httpx") # The `httpx` module is not yet loaded
httpx.get("https://google.ca/") # Loads the `httpx` module and calls `.get()`
这就是它的样子:
from functools import cached_property
from importlib import import_module
from typing import Any, TYPE_CHECKING
class Lazy:
def __init__(self, name: str) -> None:
self._name = name
def __getattr__(self, item: str) -> Any:
return getattr(self._module, item)
@cached_property
def _module(self):
return import_module(self._name)
...并且有效! 然而,像 Mypy 和 PyCharm 这样的静态类型检查器将延迟导入的
httpx
模块视为具有任何功能,因此代码如下:
from loader import Lazy
httpx = Lazy("httpx")
httpx.get(42)
httpx.woot
...没有被标记为已损坏。 PyCharm 也无法自动完成方法名称或参数,因此当代码运行时,开发起来要困难得多。
在完美的世界中,惰性加载器可以通过静态类型检查器的方式将自身替换为惰性导入的模块,但这需要static类型检查器来做dynamic的事情,所以我什至不确定这是否可以做到。
我有可用的选项吗?还是这在 Python 领域是禁忌? 通常情况下,我什至不会尝试做这样的事情,但我正在处理的代码库非常大,并且在进行本地开发时,惰性加载程序可以在很大程度上改善启动时间。
所有模块都是
types.ModuleType
类型,但每个模块都有自己的属性。您可以键入提示函数作为返回此类型的值,但您(还)不能使用类型提示来表示特定实例,除了那些允许作为 Literal[]
的参数的提示。
打个比方,这类似于:
class C:
pass
a = C()
a.foo = 'bar'
def f() -> C: # Literal[a] is invalid.
return a
c = f()
c.foo # error
解决此问题的一种方法是仅在类型检查时直接导入:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import httpx
else:
httpx = Lazy('httpx')
这是最好的方法,只要与实际模块保持一致即可。 如果存在派生(例如
_module
属性),请使用 cast()
处理它们。请注意,PyCharm 不会尊重此保护,并将尝试分析两个分支。然而,这并不是一个缺点。
如果你的推导比较多,只使用Mypy,你也可以写一个Mypy插件。理论上,Mypy 插件可以支持任何类型的动态行为。缺点是它不能与其他类型检查器一起使用,并且需要额外的维护工作。