typeguard 是专为类型检查器设计的类型守卫声明,返回 typeguard[t] 时告知检查器“若函数返回 true,则参数在后续代码中必为 t 类型”,它不改变运行时行为、不抛异常,需手动实现运行时判断且必须显式标注。

什么是 TypeGuard,它和普通类型检查函数有什么区别
TypeGuard 不是运行时抛异常的工具,而是给类型检查器(如 mypy、pyright)看的“声明”——告诉它:如果这个函数返回 True,那么传入参数**在后续代码中就一定是某个具体类型**。它本身不改变运行时行为,也不会自动 raise 异常。
常见误解是把它当 isinstance 增强版来“捕获类型错误”,但实际它只影响类型推导。若想处理异常,得额外写逻辑。
- 必须用
-> TypeGuard[T]显式标注返回类型,否则类型检查器无视其守卫语义 - 函数体里仍需手动做运行时判断(比如
isinstance(x, str)),TypeGuard不替你做这件事 - 仅在类型检查阶段生效;运行时调用它,就是个普通布尔函数
怎么写一个合法的自定义 TypeGuard 函数
核心就两点:正确标注 + 运行时判断一致。例如,想确认某个值是“非空字符串”:
from typing import TypeGuard <p>def is_nonempty_str(obj: object) -> TypeGuard[str]: return isinstance(obj, str) and len(obj) > 0 </p>
注意这里 obj: object 是推荐写法——用太窄的类型注解(比如 str)会让类型检查器提前“认定它是 str”,失去守卫意义。
- 返回类型必须是
TypeGuard[YourTargetType],不能是bool或泛型别名 - 守卫逻辑必须覆盖目标类型的全部必要条件(比如判断
str就不能漏掉isinstance) - 避免副作用:类型守卫函数不应修改输入、打印、读文件等,否则可能被静态分析误判
为什么用了 TypeGuard,mypy 还报错说“可能为 None”
典型场景:你写了 if is_nonempty_str(x): ...,但之后访问 x.upper() 仍被 mypy 报错。这往往是因为:
-
x的原始类型注解是Optional[str]或Union[str, int],而你的TypeGuard只声明了str,没排除None—— 所以检查器只敢推导出“此时x是str”,但无法否定其他分支的存在 - 你在
if块外或else分支里复用了x,而类型检查器不会跨作用域“记住”守卫结果 - 使用了不支持
TypeGuard的旧版 mypy(
验证方式:在 if 块内加一行 reveal_type(x)(mypy)或 assert_never(x)(pyright),看实际推导类型是否符合预期。
如何配合异常处理真正“处理类型异常”
TypeGuard 自身不抛异常,但你可以把它和显式错误处理组合起来。例如,拒绝非法输入并给出清晰提示:
from typing import TypeGuard
<p>def is_positive_int(obj: object) -> TypeGuard[int]:
return isinstance(obj, int) and obj > 0</p><p>def process_count(value: object) -> int:
if not is_positive_int(value):
raise TypeError(f"Expected positive int, got {type(value).<strong>name</strong>}: {value}")
return value # 此处 mypy 知道 value 是 int
</p>
这种写法既让类型检查器能推导后续类型,又在运行时提供明确失败路径。关键点:
- 守卫函数只负责“判断”,异常由调用方决定何时抛、怎么抛
- 不要在
TypeGuard函数内部 raise —— 它不该有运行时控制流责任 - 若需日志或转换(比如把
"42"转成int),应另写独立函数,而非塞进守卫里
最易被忽略的是:TypeGuard 的价值完全依赖类型检查器配置和团队协作。如果项目没开 mypy/pyright,或者有人绕过检查直接运行,那它只是个带特殊标注的 bool 函数。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











