【由DeepSeek辅助编写】
Python 3.15 在 2026 年 10 月 9 日发布(release notes)。PEP 810 带来的显式惰性导入大概是这一版里性价比最高的改动:在 import 前面加一个 lazy 关键字,模块就不在语句执行时加载,而是等你第一次真正读到这个名字才加载。对启动时间敏感的 CLI 工具、serverless 冷启动这类场景,这可能是一行关键字换来的一次优化。
先验证:import 真的被推迟了吗
mod.py 在被导入时会打印一行:
1 | print('mod loaded') |
main.py 只把 import 换成 lazy import:
1 | # uv run -p 3.15 python -m main |
在 CPython 3.15.0(Windows x64)上运行 python -m main,输出是:
1 | before calling hello |
第二行是关键:mod loaded 出现在 before calling hello 之后。如果写的是普通 import mod,它会在第一行就打印。再反过来验证一次——只写 lazy import mod、全程不碰 mod,那么 mod loaded 一次都不会出现,模块根本没有进入内存。
uv用户注意:注释里那条uv run -p 3.15 python -m main是合法写法(-p就是--python)。不过截至 uv 0.12.17,3.15会解析到3.15.0rc2;想跑正式版,直接python3.15 -m main更稳。
三种写法
1 | lazy import mod # 整个模块推迟 |
lazy from ... import ... 的语义稍微特别一点:第一次访问其中任何一个名字,就会加载整个模块,但只有被访问的那个名字会被解析成真实值,其余名字仍然是代理,直到你访问它们。
不想改 import 语句的话,可以在模块里声明 __lazy_modules__:
1 | __lazy_modules__ = ["json", "pathlib"] # 必须写全限定名 |
还可以在进程级别开关:python -X lazy_imports=all(或环境变量 PYTHON_LAZY_IMPORTS=all)会把所有普通 import 都变成惰性的;默认值是 normal,即只有显式写了 lazy 才惰性。运行时可以用 sys.set_lazy_imports() 调整。
绑定的是代理,不是模块
语句执行后,名字上绑的是一个惰性代理对象:
1 | lazy import mod |
需要提前加载时,先取出代理再显式解析:globals()['mod'].resolve()。
这里有个容易踩的点:解析发生在“读取这个名字”的时候。mod.hello() 会加载,但 type(mod)、dir(mod)、甚至 x = mod 同样会加载——只要字节码读了全局名字就会触发。真正不触发的是 globals()、无参 dir(),以及 globals()['mod'] 这种字典取值。
四个坑
- 只能在模块顶层写。 函数体、类体、
try/except/finally里出现lazy都是SyntaxError;lazy from mod import *和lazy from __future__ import ...也不允许。 - 报错时机被推迟到使用点。 模块不存在(或导入时抛异常)时,
lazy import语句本身不报错,错误在你第一次读这个名字时抛出,形状是ModuleNotFoundError,__cause__是ImportError: lazy import of 'x' raised an exception during resolution。好处是没装的可选依赖不再拖垮程序启动,坏处是报错位置离原因很远,try/except ImportError也拦不住 import 语句。 - 导入的副作用一起被推迟。 注册表、插件装饰器、
__init__.py里的初始化都不会在启动时执行。如果某个框架靠 import 完成自动发现,惰性导入会静默地改变行为——这类场景要么别用,要么改成显式发现。 - 内省看不见它。 在第一次读取之前,模块不在
sys.modules里,vars(proxy)直接抛TypeError。靠遍历sys.modules或依赖 import 副作用的工具(插件扫描、序列化框架、部分 mock 库)可能漏掉它。
什么时候值得用
PEP 810 的动机部分给出的经验值是启动时间降低 50–70%、内存降低 30–40%(这是 PEP 810 自己的说法)。它真正适合的是:
- 启动路径很长、但大部分依赖只在个别子命令里用到的 CLI;
- 冷启动敏感的 serverless 函数;
- 重依赖(numpy、pandas、torch 之类)只是可选功能的情况。
要不要全量开 -X lazy_imports=all,建议先量一下:惰性导入只是把加载成本从“启动时”挪到“第一次用”,总量不会变,变的只是分布。如果程序启动后马上就要用到所有依赖,收益就是零,还要额外承担上面那几个坑。
参考: