Python 3.11 移除 importlib.util.LazyLoader 因其线程不安全且与 PEP 562/690 冲突,推荐用 getattr 实现模块级懒加载或 importlib.import_module 加缓存实现函数级懒加载。

Python 3.11 原生不支持 lazy-loader 模块懒加载,importlib.util.LazyLoader 已被移除,强行复用旧版代码会触发 AttributeError: module 'importlib.util' has no attribute 'LazyLoader'。
为什么 Python 3.11 删掉了 LazyLoader
它在早期 CPython 实现中存在线程安全缺陷(模块状态竞争),且与 PEP 562(__getattr__ 模块级自定义导入)和 PEP 690(延迟模块初始化)的设计目标冲突。官方转向更可控、更显式的惰性策略——你得自己动手,但方式更可靠。
用 __getattr__ 实现安全的模块级懒加载
这是 Python 3.11 推荐的替代方案:在包的 __init__.py 中定义 __getattr__,首次访问属性时才触发真实导入。它天然线程安全,且不会污染 sys.modules 提前。
常见错误现象:ImportError 在模块导入时就抛出,而不是首次使用时——说明用了错误的时机(比如在顶层写 import heavy_module)。
立即学习“Python免费学习笔记(深入)”;
- 只在
__getattr__内部做import,不要在函数外执行 - 必须抛出
NameError(而非ImportError)来触发__getattr__回调 - 避免循环引用:不要在
__getattr__里又去访问当前模块的其他未定义名 - 示例(
mylib/__init__.py):
def __getattr__(name):
if name == "pandas":
import pandas
return pandas
elif name == "torch":
import torch
return torch
raise NameError(f"name {name!r} is not defined")
用 importlib.import_module + 缓存实现函数级懒加载
适合工具函数封装场景,比如 CLI 工具中只有部分子命令需要重型依赖。比模块级更细粒度,也更容易单元测试。
性能影响:首次调用有 ~0.5–3ms 导入开销(取决于模块大小),后续调用无额外成本(缓存到 globals() 或闭包变量)。
- 别用
eval()或exec()动态导入——不安全且无法被静态分析工具识别 - 推荐缓存到模块级变量,而非每次重新 import(避免重复初始化副作用)
- 注意路径拼接:用
importlib.import_module("package.submodule"),不是文件路径 - 示例:
_heavy_dep = None <p>def run_ml_task(): global _heavy_dep if _heavy_dep is None: import sklearn.ensemble _heavy_dep = sklearn.ensemble return _heavy_dep.RandomForestClassifier()
容易被忽略的兼容性细节
__getattr__ 方式在 Python 3.7+ 可用,但 Python 3.11 对其行为做了更严格的校验:如果模块中已定义了同名变量(哪怕值为 None),__getattr__ 就不会被调用。这意味着你不能“预留占位符”。
另一个坑是 IDE 和类型检查器(如 mypy、pylance)默认不理解 __getattr__ 的动态导出,需手动加 # type: ignore 或提供 py.typed + stub 文件。
真正麻烦的不是怎么写,而是怎么让团队其他成员意识到:这个模块“看着导入了,其实没真动”,调试时别在 import 行打断点——它根本不会停在那里。



















