Appearance
Python 3.15 Explicit Lazy Imports(PEP 810)
Python 3.15 引入
lazysoft keyword,讓 import 的實際載入延後到首次存取時才觸發,CLI 啟動可加速 2–3 倍,透過自適應特化技術使載入後 overhead 趨近於零。
概述
Python 的 import 語意向來清晰——「import 當下就讀檔、編譯、執行模組頂層」——但在現代應用中這卻是效能瓶頸。單純頂層的 import pandas、import torch、import boto3 就可能在業務邏輯執行前消耗數秒啟動時間,對 CLI 工具、AWS Lambda 冷啟動、Jupyter Notebook、ML 訓練腳本影響尤為明顯。
過去十年社群發展出多種 workaround(函式內部 import、TYPE_CHECKING 守衛、importlib.util.LazyLoader、PEP 562 __getattr__ hook),但沒有任何一種能同時做到語法乾淨、靜態分析工具認得、且載入後零 overhead。PEP 810 於 2025-11-03 由 Python Steering Council 通過,預計隨 Python 3.15(2026-10 釋出)正式落地。
PEP 810 的前身是 2022 年 Meta 提出的 PEP 690,當時設計為「全域、隱式」的自動開關。Steering Council 因「無清楚 opt-in 邊界會大規模破壞 registry pattern 等仰賴 import 時機的程式碼」而否決。Meta 的 Cinder 分支在 Instagram 後端與 ML workload 跑了多年工程驗證後,2025 年以「顯式、opt-in、不傳染」重新設計為 PEP 810,順利通過。
核心內容
語法設計
lazy 是 soft keyword(脈絡敏感關鍵字)——只在 import/from 前才被解析為關鍵字,其他位置仍是合法識別字,確保升級到 3.15 不會破壞任何現有使用 lazy 作為變數名的程式碼。
python
# 合法用法
lazy import pandas
lazy import pandas as pd
lazy from pathlib import Path
lazy from pathlib import Path, PurePosixPath
lazy from . import submodule # 相對 import
lazy from ..parent import helper # 多層相對 import
# lazy 仍然可作識別字
def lazy(func): ...
lazy = SomeObject()語法限制(編譯期 SyntaxError)
| 禁止情境 | 原因 |
|---|---|
| 函式 / 類別內 | 確保 lazy 行為等同模組層級命名綁定;若允許,每次呼叫都重建 proxy,語意混亂 |
try/except 內 | 延後的 import error 無法被原 try block 攔截 |
from x import * | 星號 import 必然觸發完整載入,與 lazy 衝突 |
from __future__ import | 必須在編譯期生效,邏輯上不可 lazy |
底層機制
編譯階段:
lazy import json與普通import json共用IMPORT_NAME指令,差別只在 oparg 的 lazy bit(oparg & 0x01)設為 1,不需要新的指令家族。首次綁定:執行
IMPORT_NAME時若 lazy bit 為真,解譯器建立types.LazyImportType代理物件(proxy)綁到名稱,不讀檔、不 disk I/O、不編譯。首次解引用:程式碼第一次寫
json.dumps(...)時,LOAD_GLOBAL指令偵測到LazyImportType,呼叫 C 層的_PyImport_LoadLazyImportTstate()完成真正的載入,用實體模組覆蓋 proxy。自適應特化(PEP 659):同一個
LOAD_GLOBAL連續存取 2–3 次後,CPython 將指令特化為LOAD_GLOBAL_MODULE,跳過所有 lazy 檢查,之後的每次存取與「從未 lazy 過」完全相同。效能測試中 reification overhead 落在 ±0.5%,在量測雜訊範圍內。
效能數據
| 指標 | 改善幅度 |
|---|---|
| CLI 應用啟動時間 | 減少 50–70% |
| 記憶體使用 | 減少 30–40% |
| PySide6 等 GUI 應用 | 啟動減少 10–20% |
| AWS Lambda 冷啟動 | 縮短 150–400 ms |
pypistats --help(社群實測,已手動優化過的 codebase) | 104 ms → 35.7 ms(約 2.92×) |
| 執行期 overhead(reification 後) | ±0.5%(量測雜訊) |
全域控制
不改動程式碼即可快速測試效益:
bash
# 環境變數(最低優先序)
PYTHON_LAZY_IMPORTS=all python myapp.py
# 命令列 flag(中優先序)
python -X lazy_imports=all myapp.pypython
# sys API(最高優先序,可動態切換)
import sys
sys.set_lazy_imports("all") # all / normal / none
sys.set_lazy_imports("none")
sys.get_lazy_imports()
# 過濾器:細粒度排除有副作用的框架
def my_filter(importer: str, name: str, fromlist: tuple | None) -> bool:
return name not in {"django", "flask"} # False = 強制 eager
sys.set_lazy_imports_filter(my_filter)| 模式 | 行為 |
|---|---|
normal(預設) | 只有明確寫 lazy 的語句才延遲 |
all | 所有模組層級 import 都 potentially lazy(星號與 try-block 內除外) |
none | 忽略 lazy 關鍵字,全部 eager |
模組層級宣告:__lazy_modules__
函式庫作者可在模組頂層宣告哪些依賴應被視為 lazy 候選:
python
# my_library/__init__.py
__lazy_modules__ = {"pandas", "pyarrow", "torch", "boto3"}
import pandas # Python 3.15+ 自動 lazy
import torch # 同上
import os # 不在清單,正常 eagerPython 3.14 及更早版本完全忽略 __lazy_modules__ 屬性,不報錯,向後相容,函式庫作者今天就可以加。
與既有方案的比較
| 方案 | 語法簡潔 | 工具支援 | 載入後 Overhead | 主要限制 |
|---|---|---|---|---|
| 函式內部 import | ❌ | ✓ | sys.modules 查表 | 失去頂層依賴清單;可維護性差 |
if TYPE_CHECKING: | ❌ | ✓ | 0 | 不能 runtime 呼叫 |
importlib.util.LazyLoader | ❌ 大量樣板 | ❌ | 中 | 靜態分析看不出來 |
PEP 562 __getattr__ hook | ❌ | ❌ | 中 | 工程複雜、易出 bug |
PEP 810 lazy import | ✓ | ✓(mypy/ty/IDE) | ≈ 0 | 不能在函式/類別/try 內 |
PEP 810 的關鍵優勢是「工具支援」——因為 lazy 寫進語法,型別檢查器、IDE、linter 都能準確識別,這是任何函式庫層級方案做不到的。
避坑要點
副作用模組:任何 import 時有頂層副作用的模組(logging.basicConfig()、registry 裝飾器、monkey-patch)不能 lazy,副作用會延後或永遠不發生。
Registry Pattern:Flask blueprint、pytest plugin、SQLAlchemy model 等依賴「import 即註冊」的模式,若 lazy 會使物件從未出現在 registry 中。官方建議改為顯式 discovery 函式:
python
import framework
framework.discover_plugins(["my_plugin"]) # 明確觸發載入與註冊錯誤延後:lazy import pnadas(typo)不會在這行報錯,而會在首次使用時才拋 ModuleNotFoundError。CI 必須有完整的 import 路徑測試。
子模組:lazy import foo 後存取 foo.bar.Baz() 可能不可靠;應顯式 lazy import foo.bar。
關鍵要點
lazy是 soft keyword,不破壞現有程式碼中任何名為lazy的識別字- 只能用於模組頂層;函式/類別/try-block 內會在編譯期立即 SyntaxError
- 自適應特化後執行 overhead 趨近於零,是「整筆勾消」不是「延遲付款」
PYTHON_LAZY_IMPORTS=all可不改程式碼快速評估效益,也是找出有副作用模組的最佳工具__lazy_modules__宣告向後相容,函式庫作者現在就可以加- 副作用模組、registry pattern 必須保持 eager 或改用顯式 discover API
實際應用
最適合的場景:CLI 工具、AWS Lambda、Jupyter Notebook、ML 訓練腳本——任何對啟動時間敏感的場合。
升級準備步驟:
- 在 CI 加
PYTHON_LAZY_IMPORTS=all跑完整測試套件,失敗的就是需要保持 eager 的模組 - 用
lazycheck掃描 registry pattern、monkey-patch 等潛在踩雷模式(12 條內建規則) - 關注 mypy(issue #20978)、ruff、ty(issue #2968)、isort(issue #2462)的支援進度
相關概念
- uv Python 版本與環境管理 — Python 版本與環境工具
- Loguru + Redis 集中日誌 — 現代 Python Logging 最佳實踐
來源
- PEP 810 – Explicit lazy imports — 官方提案全文
- Python 3.15 What's New — 官方更新說明
- Three times faster with lazy imports — Hugo van Kemenade (2025-10-19) — 社群實測 2.92×
- The Story of Python's Lazy Imports — techlife.blog — PEP 690 → PEP 810 歷史脈絡
- lazycheck — PEP 810 Lazy Import Readiness Checker — 升級前掃描工具
- 原始素材:2026-05-25 Python 3.15 Explicit Lazy Imports 完整解析.md