Python无内置懒加载,需手动实现:函数内import实现按需加载;用find_spec预检可选依赖;模块级__getattr__支持属性级懒加载。

Python 中没有内置的 lazy loading 机制
Python 导入模块是同步且立即执行的,import 语句一执行,模块顶层代码就运行、类就定义、函数对象就创建。所谓“懒加载”,其实是靠手动控制导入时机来模拟——不是语言特性,而是编码策略。
用函数封装 import 实现按需加载
最常用也最可控的方式:把 import 放进函数内部,首次调用时才触发加载。适用于启动慢、使用频次低或非必选依赖的模块(比如 torch、matplotlib、psycopg2)。
常见错误现象:ModuleNotFoundError 在模块真正用到前就抛出,说明 import 写在了函数外;或者反复调用函数导致重复 import(虽不报错,但浪费)。
- 推荐写法:在函数内做一次检查 + 缓存模块引用
-
import语句本身是幂等的,但模块初始化逻辑(如__init__.py中的代码)仍会重复执行,所以缓存module对象更稳妥 - 不要用
globals().setdefault()这类魔术方式,可读性和调试性差
def get_torch():
global _torch
if '_torch' not in globals():
import torch as _torch
return _torch
<h1>后续调用 get_torch() 才真正导入</h1><p>立即学习“Python免费学习笔记(深入)”;
用 importlib.util.find_spec() 判断模块是否可用再加载
当模块是可选依赖(比如用户没装 pydantic 但你的库想支持它),不能直接 import,否则会崩。这时应先探测是否存在,再决定是否加载。
注意:find_spec() 不会执行模块代码,比 try/except ImportError 更轻量,也更适合做预检。
-
find_spec("requests")返回ModuleSpec对象表示存在;返回None表示未安装或不可见 - 搭配
importlib.util.module_from_spec()+spec.loader.exec_module()可实现完全手动加载(极少需要) - 避免在循环或 hot path 中频繁调用
find_spec,有 I/O 开销
import importlib.util
<p>if importlib.util.find_spec("pydantic"):
from pydantic import BaseModel
else:
BaseModel = None # 或定义空基类作 fallback
类属性级懒加载:用 __getattr__ 拦截未定义属性
Python 3.7+ 支持模块级 __getattr__,可用于实现“首次访问某属性时才导入对应模块”。适合大工具包(如 sklearn 子模块)的按需加载设计。
容易踩的坑:__getattr__ 是兜底方法,仅在常规属性查找失败后触发;一旦模块被真实导入并赋值到 sys.modules,后续访问就不再走它——所以必须确保导入后显式缓存到模块命名空间。
- 必须定义在模块顶层,且函数签名严格为
def __getattr__(name): - 不能用于已存在的名称(比如你写了
foo = 1,再访问foo就不会触发__getattr__) - 抛出
AttributeError表示该属性确实不存在,否则会被静默吞掉
# mypackage/__init__.py
def __getattr__(name):
if name == "plotting":
import mypackage.plotting as _plotting
globals()["plotting"] = _plotting
return _plotting
raise AttributeError(f"module {__name__} has no attribute {name}")
真正难处理的是跨进程或热重载场景下的模块状态一致性——import 是全局的,一旦加载就驻留 sys.modules,没法“卸载”。所谓懒,只是推迟,不是隔离。


















