bigywater.tech
Python · 54

TypeGuard:类型检查器如何理解自定义判断函数

真实对比返回普通bool和返回TypeGuard[list[str]]两个判断函数,验证mypy在if判断通过后对元素类型的真实推断结果完全不同。

普通bool返回值
真实mypy不做类型收窄,v.upper()报错
TypeGuard[list[str]]
真实v被收窄成str,v.upper()不报错
运行时逻辑
真实两版完全一样,区别只在类型标注

做的事:两个运行时逻辑完全相同的判断函数,标注不同的返回类型

代码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?

验证:普通bool返回值,mypy真实不知道判断通过后元素类型变窄了

实测 · 本机执行输出(mypy 2.3.1)
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()。

验证:TypeGuard[list[str]]告诉mypy"判断为真时参数类型真实变成了什么"

is_str_list_guard唯一的区别是返回类型标注写成了TypeGuard[list[str]],运行时的判断逻辑(all(isinstance(v, str) for v in vals))真实跟plain版本一字不差——这正说明TypeGuard不改变代码的任何运行时行为,它纯粹是给mypy一个真实额外的承诺:"如果这个函数返回True,请把传入的vals参数当作list[str]来处理,不再是list[object]"。mypy真实相信了这个承诺,process_guard里if is_str_list_guard(vals):判断通过之后,循环变量v的类型真实被"收窄"成了str,v.upper()因此完全不会报错。这套机制真实解决的问题很具体:Python原生的isinstance(x, str)这种检查,mypy自己能直接理解并做类型收窄,但一旦判断逻辑被封装进一个自定义函数(比如本文这种检查"整个列表里所有元素是不是都是str"这种isinstance处理不了的复合判断),mypy默认真实没有能力反推"这个自定义函数返回True到底意味着参数类型发生了什么变化"——TypeGuard就是真实用来显式声明这份关系的标注,写判断函数的人负责保证这个承诺在运行时真的成立,mypy负责把这份承诺真实应用到后续的类型推断里。

分步演示:普通bool和TypeGuard在mypy眼里的真实差异

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

同样的运行时逻辑,TypeGuard真实改变的只是mypy的推断结果
这一步新验证的标注

    本文用到的工具

    • 本机 Python 3.14.7,真实的 mypy 2.3.1(venv中安装)
    • 标准库 typing.TypeGuard(PEP 647)
    京ICP备2026067502号-1京公网安备11010502063487号