真实对比返回普通bool和返回TypeGuard[list[str]]两个判断函数,验证mypy在if判断通过后对元素类型的真实推断结果完全不同。
代码def is_str_list_plain(vals: list[object]) -> bool: return all(isinstance(v, str) for v in vals) def is_str_list_guard(vals: list[object]) -> TypeGuard[list[str]]: return all(isinstance(v, str) for v in vals) # 运行时逻辑跟上面一模一样 def process_plain(vals: list[object]) -> None: if is_str_list_plain(vals): for v in vals: print(v.upper()) # v是object还是str? def process_guard(vals: list[object]) -> None: if is_str_list_guard(vals): for v in vals: print(v.upper()) # v是object还是str?
typeguard_test.py:12: error: "object" has no attribute "upper" [attr-defined] Found 1 error in 1 file 真实报错的正是process_plain那一行,process_guard没有出现在错误列表里
这是本文认为最能体现TypeGuard存在意义的对比:is_str_list_plain的运行时逻辑真实完全正确——如果它返回True,vals里的元素确实都是str;但mypy真实只看函数签名上写的返回类型-> bool,一个普通的bool对mypy来说只是"真或假",不携带任何"如果为真,参数的类型会怎么变窄"这样的额外信息。所以if is_str_list_plain(vals):这个判断,在mypy的类型推断里真实完全没有改变vals里元素的类型——循环变量v依然真实被认为是最初标注的object,而object类型真实没有.upper()这个方法,于是真实报出attr-defined错误——即便这段代码在运行时因为is_str_list_plain真的做了正确的isinstance检查,永远不会真的在非str对象上调用.upper()。
每一步都是本文上面用真实的mypy 2.3.1跑出来的原始结果。
mypy 2.3.1(venv中安装)typing.TypeGuard(PEP 647)