Skip to content

Python 3.15 Explicit Lazy Imports(PEP 810)

Python 3.15 引入 lazy soft keyword,讓 import 的實際載入延後到首次存取時才觸發,CLI 啟動可加速 2–3 倍,透過自適應特化技術使載入後 overhead 趨近於零。

概述

Python 的 import 語意向來清晰——「import 當下就讀檔、編譯、執行模組頂層」——但在現代應用中這卻是效能瓶頸。單純頂層的 import pandasimport torchimport 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,順利通過。

核心內容

語法設計

lazysoft 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

底層機制

  1. 編譯階段lazy import json 與普通 import json 共用 IMPORT_NAME 指令,差別只在 oparg 的 lazy bit(oparg & 0x01)設為 1,不需要新的指令家族。

  2. 首次綁定:執行 IMPORT_NAME 時若 lazy bit 為真,解譯器建立 types.LazyImportType 代理物件(proxy)綁到名稱,不讀檔、不 disk I/O、不編譯。

  3. 首次解引用:程式碼第一次寫 json.dumps(...) 時,LOAD_GLOBAL 指令偵測到 LazyImportType,呼叫 C 層的 _PyImport_LoadLazyImportTstate() 完成真正的載入,用實體模組覆蓋 proxy。

  4. 自適應特化(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.py
python
# 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       # 不在清單,正常 eager

Python 3.14 及更早版本完全忽略 __lazy_modules__ 屬性,不報錯,向後相容,函式庫作者今天就可以加

與既有方案的比較

方案語法簡潔工具支援載入後 Overhead主要限制
函式內部 importsys.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 訓練腳本——任何對啟動時間敏感的場合。

升級準備步驟

  1. 在 CI 加 PYTHON_LAZY_IMPORTS=all 跑完整測試套件,失敗的就是需要保持 eager 的模組
  2. lazycheck 掃描 registry pattern、monkey-patch 等潛在踩雷模式(12 條內建規則)
  3. 關注 mypy(issue #20978)、ruff、ty(issue #2968)、isort(issue #2462)的支援進度

相關概念

來源