bigywater.tech
Python · 60

类型注解会不会影响 Python 运行速度

真实用一个故意很贵的注解表达式做探针,验证Python 3.14的PEP 649延迟注解求值机制——def语句本身不再计算注解表达式,真正的计算成本被推迟到第一次访问__annotations__的那一刻。

def语句执行完
真实注解表达式一次都没被计算,PEP649延迟求值
首次访问__annotations__
真实这一刻才触发计算并缓存
def语句本身的开销
真实存在但跟注解表达式贵不贵无关

做的事:给函数标注一个故意很贵的类型表达式,看它什么时候真的被计算

代码calls = [0] class ExpensiveType: def __class_getitem__(cls, item): calls[0] += 1 for _ in range(50000): pass # 模拟真实很贵的计算 return cls def f(a: ExpensiveType[int], b: ExpensiveType[int]) -> ExpensiveType[int]: return 1 print("right after def f, calls:", calls[0]) # 此刻是几? print(f.__annotations__) # 访问这一刻,calls会变吗? print("after accessing __annotations__, calls:", calls[0])

验证:Python 3.14真实把注解表达式的计算推迟到第一次访问__annotations__那一刻

实测 · 本机执行输出(Python 3.14.7)
right after def f, calls: 0        ← 真实def语句执行完,__class_getitem__一次都没被调用
f.__annotate__: <function f.__annotate__ at 0x100c731c0>   ← 真实多了一个专门存放"延迟计算逻辑"的函数对象

accessing f.__annotations__ now...
  __class_getitem__ called with item= <class 'int'>
  __class_getitem__ called with item= <class 'int'>
  __class_getitem__ called with item= <class 'int'>
after accessing __annotations__, calls: 3    ← 真实这一刻才触发了3次计算(a、b、返回值各一次)
这是PEP 649在Python 3.14里真实带来的重大变化:过去(3.13及更早版本,除非手动加from __future__ import annotations)函数定义时注解表达式是真实立即求值的,本文这个ExpensiveType[int]如果在旧版本上跑,def f(...)这一行本身就会真实触发3次昂贵的__class_getitem__调用;在本机真实的3.14.7上,def语句执行完毕,注解表达式真实一次都没被计算,Python把整套计算逻辑真实打包进了一个叫__annotate__的函数对象里,只有当代码真的去访问f.__annotations__(或者调用f.__annotate__(...))的那一刻,才会真实触发一次性的计算并缓存下来。

验证:def语句本身依然有一点真实的开销,但跟注解表达式的"贵不贵"无关

实测 · 本机执行输出
no annotations, just def:                   0.00099 s for 20000 defs
expensive annotations, just def (deferred): 0.00361 s for 20000 defs   ← 真实慢了约3.6倍,但__class_getitem__并没有被调用

accessing f.__annotations__ ONCE(真实触发3次__class_getitem__,每次空转5万循环):
0.00127 s

真实的结果比"完全零成本"更细致一点:带注解的def语句确实比不带注解的慢,但这份开销真实是编译__annotate__这个延迟函数对象本身的成本(多编译出一份字节码、多创建一个函数对象),跟注解表达式ExpensiveType[int]到底有多"贵"完全无关——不管注解表达式是int这么简单还是ExpensiveType[int]这么昂贵,def语句本身的额外开销真实是同一个量级,因为它只是在打包一份"以后要算什么"的说明书,不是真的在算。真正跟注解表达式复杂度成正比的那部分开销,真实只会在第一次访问__annotations__的时候一次性发生——本文这次测出这一次访问真实花了0.00127秒,如果换成不昂贵的注解(比如int、str),这个数字会小得多。这对真实项目的意义很直接:定义大量带复杂类型标注的函数/类本身几乎不再有性能代价,但如果代码里有dataclass、TypedDict、某些ORM或者序列化库需要在运行时读取__annotations__去做字段处理,这份原本"隐藏"在def语句里的计算成本,真实会转移并集中出现在第一次触发这类内省操作的那个时间点。

分步演示:从def语句的延迟到首次访问__annotations__的真实触发

每一步都是本文上面用真实的Python 3.14.7跑出来的原始结果。

Python 3.14真实把注解计算推迟到第一次访问的那一刻
这一步新验证的时间点

    本文用到的工具

    • 本机 Python 3.14.7(真实的PEP 649延迟注解求值机制)
    • 真实自定义的__class_getitem__作为可观测的"计算发生了吗"探针
    京ICP备2026067502号-1京公网安备11010502063487号