# Python 3.15 的 lazy import，启动慢是不是先把重模块标成 lazy 就行？

**URL:** <https://www.shiqidu.com/t/topic/1924>\
**Category:** 开发调优\
**Tags:** 问与答, Python, 编程\
**Created:** [2026 年11 月 4 日 04:20 UTC](https://www.shiqidu.com/t/topic/1924 "2026-11-04T04:20:09Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![shiqidu\_editor](https://www.shiqidu.com/user_avatar/www.shiqidu.com/shiqidu_editor/32/528_2.png) [@shiqidu\_editor](https://www.shiqidu.com/u/shiqidu_editor)\
发布日期： [2026 年11 月 4 日 04:20 UTC](https://www.shiqidu.com/t/topic/1924/1 "2026-11-04T04:20:09Z")

</div>

光跑 `--help` 也要等一截——命令行工具加了几个可选子命令之后，导入树太深，很多模块这一趟根本用不上，却在启动时全加载了。以前常见权宜之计是把 import 塞进函数里，可读性和静态分析都别扭。Python 3.15 引入了显式的 `lazy import`（PEP 810），可以把「声明放在文件顶」和「真正加载拖到第一次用」拆开。[python.org](http://python.org) 下载列表里，3.15 系列当前补丁是 3.15.2（3.15.0 发布于 2026 年 10 月 9 日，之后有 3.15.1 / 3.15.2）。下面说的行为以 [What’s New in Python 3.15](https://docs.python.org/3.15/whatsnew/3.15.html) 和 [PEP 810](https://peps.python.org/pep-0810/) 为准，不谈默认 UTF-8 那条已经单独写过的改动。

## 语法和推迟点

可以直接写：

```python
lazy import json
lazy from pathlib import Path

```

名字立刻绑到当前模块，真正加载拖到第一次用这个名字。加载失败时，异常出现在首次使用处，而不是 import 那一行。回溯里会带上原先的 lazy import 位置，方便对。`lazy from module import a, b` 时，第一次用到其中一个名字会加载整个模块，并只把那一个名字实体化，另一个仍保持代理，直到被访问。代理类型是 `types.LazyImportType`，需要程序化探测时可以用它。

只允许在模块顶层。函数里、类体里、`try` / `except` / `finally` 里写 `lazy`，或 `lazy from … import *`、`lazy from __future__ import …`，都会 `SyntaxError`。也就是说，想在函数里临时懒加载，还是得用传统的函数内 import，或者把声明挪回模块顶并加 `lazy`。

## 全局旗标和 filter

不想改源码、又想整进程偏懒，可以用 `-X lazy_imports=all` 或环境变量 `PYTHON_LAZY_IMPORTS=all`。`normal`（默认）只尊重源码里的 `lazy` 关键字。`none` 则强迫全部急切加载，方便排查。运行时还有 `sys.set_lazy_imports()` / `sys.get_lazy_imports()`。官方把全局开关和 filter 定位成偏应用 / 运维侧的高级能力，库作者一般不该在 import 时偷偷改全局模式。

更细的是 `sys.set_lazy_imports_filter()`：回调收到导入方、被导入模块名、fromlist，返回 `True` 才允许继续懒加载。常见思路是「自家包前缀走 lazy，第三方保持 eager」，避免有些库依赖 import 时副作用（注册插件、改全局状态）被推迟搞乱：

```python
import sys

def myapp_filter(importing, imported, fromlist):
    return imported.startswith("myapp.")

sys.set_lazy_imports_filter(myapp_filter)
sys.set_lazy_imports("all")

```

还要兼容旧版本时，可以在模块里定义 ` __lazy_modules__ ` 列表。3.15+ 会把列表里的普通 import 当成 lazy，老解释器会忽略这个名字。官方把它定位成过渡手段，新代码优先直接写 `lazy` 关键字。

## 别指望的东西

- 不承诺「启动一定少几秒」。收益取决于有多少导入其实用不到，以及副作用能不能安全延后。CLI 多子命令、可选重依赖这类场景通常更有感，但具体数字因项目而异。
- `all` 一开，带注册副作用的第三方库可能表现怪异。出问题就用 filter 拉回 eager，或临时 `lazy_imports=none` 排查。
- 注解专用的 import 很适合标 lazy，常能少写一层 `TYPE_CHECKING` 护栏，但不等于可以省掉对副作用模块的审查。
- `from module import *` 不能 lazy。全局 `all` 模式下星号导入仍是急切的。
- 懒加载只推迟「何时 import」，不改变「一次会执行整个模块」。`lazy from heavy import OneClass` 仍然会在首次使用时跑完整个 `heavy`。

落地顺序可以很保守：先给真正拖慢启动、又确定无 import 副作用的可选依赖加 `lazy`。再用 `-X importtime` 一类手段看还剩哪些大户。最后才考虑进程级 `all` 加 filter。类型检查器一般仍把 lazy import 当普通名字解析，注解场景往往最省事。热路径上第一次访问会付一次真实 import 成本，之后自适应解释器会把慢路径特化掉，所以别把「懒」理解成每次访问都要重新判断。

你们升 3.15 以后，是逐个加 `lazy`，还是直接 `-X lazy_imports=all` 再 filter？有没有库一懒加载就注册丢了的？
