该用import而非延迟加载的情况是模块在启动时就被高频调用(如json、os),此时函数内import会增加重复开销且隐晦依赖;真正适合延迟加载的是启动不用、运行中可能用且导入成本高的模块(如torch、cv2)。

什么时候该用 import 而不是延迟加载?
模块延迟加载不是银弹。如果模块在启动时就被高频调用(比如 json、os),硬塞进函数里做 import 反而增加重复开销,还让依赖关系变隐晦。真正适合延迟加载的,是那些「启动时不需、运行中可能用、且导入成本高」的模块,比如 torch、tensorflow、cv2 或大型 CLI 工具依赖的 click 子模块。
函数内 import 是最简单可靠的延迟方式
Python 的 import 语句本身是惰性的——只要没执行到那行,就不会触发加载。把 import 放进函数体里,是最轻量、最兼容、最易 debug 的做法。
常见错误:把 import 放在类方法里但写成类属性或模块级变量,结果一导入模块就执行了。
- ✅ 正确:
def process_image():<br> import cv2 # 每次调用才加载<br> return cv2.imread("test.jpg") - ❌ 错误:
class Processor:<br> cv2 = __import__("cv2") # 导入发生在类定义时 - ⚠️ 注意:多次调用会重复 import,但 CPython 会缓存已加载模块,实际开销极小;若担心,可加
if "cv2" not in sys.modules:判断(通常没必要)
用 importlib.import_module() 动态控制路径和条件
当模块名是字符串、需要按环境切换、或想捕获导入失败时,importlib.import_module() 比 import 更灵活。
典型场景:不同后端("backend.gpu" vs "backend.cpu")、插件系统、或 fallback 导入(比如优先试 orjson,失败再用 json)。
- 必须传入完整模块路径,不支持相对导入(如
".utils") - 返回的是模块对象,不是自动注入到当前命名空间,需显式赋值:
try:<br> json_mod = importlib.import_module("orjson")<br>except ImportError:<br> json_mod = importlib.import_module("json") - 注意缓存:
importlib.import_module()同样受sys.modules缓存保护,不会重复初始化
全局延迟加载封装要小心循环引用和线程安全
如果多个函数都要用同一个重型模块,又不想每处都写 import,可以封装一个带缓存的 getter。但这里容易踩坑:
- 避免在模块顶层用
global+ 函数内赋值,容易引发导入时序问题 - 多线程下首次导入可能并发触发,虽然
import本身线程安全,但自定义缓存逻辑需加锁(threading.Lock) - 更稳妥的做法是用
functools.cached_property(Python 3.8+)或typing.LazyImport(3.12+ 实验性,暂不推荐生产) - 示例安全封装:
_torch_mod = None<br>_torch_lock = threading.Lock()<br><br>def get_torch():<br> global _torch_mod<br> if _torch_mod is None:<br> with _torch_lock:<br> if _torch_mod is None:<br> import torch<br> _torch_mod = torch<br> return _torch_mod
真正麻烦的从来不是“怎么写”,而是“在哪导入”和“谁会先触发它”——尤其在包结构复杂、有 __init__.py 侧加载逻辑时,延迟导入反而可能掩盖真正的初始化顺序问题。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











