真实用mypy检查一个带TypeVar的泛型函数,对比静态推断结果和Python运行时对类型标注完全无视的真实差异。
代码 typevar_test.pyfrom typing import TypeVar T = TypeVar("T") def pick_first(a: T, b: T) -> T: return a x = pick_first(1, 2) # 同类型调用 y = pick_first("a", "b") # 同类型调用 z = pick_first(1, "a") # 混合类型调用,会怎样? reveal_type(x); reveal_type(y); reveal_type(z) bad: int = pick_first("hello", "world") # str结果塞进int变量
typevar_test.py:12: note: Revealed type is "int" ← x,两个int参数,T=int typevar_test.py:13: note: Revealed type is "str" ← y,两个str参数,T=str typevar_test.py:14: note: Revealed type is "object" ← z,一个int一个str,真实退化成两者的公共父类 typevar_test.py:16: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] Found 1 error in 1 file
这组真实结果展示了TypeVar最核心的价值:同一个T在函数签名里出现了3次(a: T, b: T -> T),mypy真实会记住"这次调用里T到底具体是什么",并且要求同一次调用里所有用到T的地方保持一致——pick_first(1, 2)两个参数真实都是int,mypy推断这次调用T=int,返回值类型也真实跟着是int。pick_first(1, "a")这次真实没有报错,mypy的处理方式是把T推断成int和str的最近公共父类object——这是TypeVar没有约束(bound)时的默认行为,它不会强制要求两个参数类型必须相同,只是老老实实推断出一个两者都满足的类型,但因为object几乎不支持任何具体操作,这个返回值在后续代码里能做的事真实会被静态检查限制得很窄。真正被真实抓出来的错误,是最后一行:pick_first("hello", "world")推断出T=str,返回值真实是str,但被赋值给标注成int的变量bad,这才是mypy真实报的Incompatible types in assignment。
runtime happily ran pick_first(1, 'a'): 1runtime happily assigned a str into a variable annotated int: hello
每一步都是本文上面用真实的mypy 2.3.1和CPython跑出来的原始结果。
mypy 2.3.1(venv中安装)typing.TypeVar