真实对比有标注和无标注的函数参数在调用同一个错误方法时mypy的真实反应差异,找出Any隐式推断这个最常见的检查盲区。
代码def untyped_param(x): # 没有任何类型标注 return x.upper() + x.nonexistent_method() # 真实不存在的方法 def typed_param(x: str) -> str: return x.upper() + x.nonexistent_method() # 同样真实不存在的方法 x = [] # 空列表,没有标注 x.append(1) x.append("mixed") # 真实会被抓出来吗 reveal_type(x)
mypy_inference_test.py:5: error: "str" has no attribute "nonexistent_method" [attr-defined] ← 真实只有typed_param(x: str)这一行被抓出来了 untyped_param里同样的x.nonexistent_method():真实完全没有出现在错误列表里!
x = []
x.append(1) ← 真实第一次append,mypy据此推断x是list[int]
x.append("mixed") ← error: Argument 1 to "append" has incompatible type "str"; expected "int" [arg-type]
reveal_type(x) → note: Revealed type is "list[int]"这组结果验证了mypy对x = []这种没有显式标注的空容器,真实采用的是一种"根据首次实际使用推断"的策略——单独一行x = []本身还看不出x该是list[int]还是list[str],mypy真实会先给它一个临时的、宽松的类型,等看到x.append(1)这一步,才真实把x的类型收窄确定成list[int];后续任何跟这个已经确定下来的类型不匹配的操作(本文这次是x.append("mixed")),真实会被正常抓出来。这跟上一节的Any盲区不是同一类问题——这里mypy是真实有能力推断出具体类型的,只是推断的时间点是"看到第一次实际用法"而不是"看到声明的那一刻";真正推断不出类型、彻底放弃检查的场景,往往真实集中在缺少参数/返回值类型标注(本文上一节验证的情形)、调用了没有类型信息的第三方库、或者显式用了Any/cast这几种真实边界。
每一步都是本文上面用真实的mypy 2.3.1跑出来的原始结果。
mypy 2.3.1(venv中安装)