如何让mypy识别始终为Falsy的对象并正确执行类型检查
我们先来看这个实际场景:定义了一个自定义的“假值类型”FalsyType,它的__bool__方法固定返回False,然后变量a被标注为FalsyType和str的联合类型。代码示例如下:
import typing as t class FalsyType: def __bool__(self) -> t.Literal[False]: return False Falsy = FalsyType() a: t.Union[FalsyType, str] a = Falsy mylist: t.List[str] = [] if a: mylist.append(a) # Mypy会在这里抛出类型错误 a = "Hello" if a: mylist.append(a)
用Mypy 0.910(Python 3.8.8环境)做类型检查时,会出现如下错误提示:
16:19: error: Argument 1 to "append" of "list" has incompatible type "FalsyType"; expected "str" [arg-type]
现在的核心问题是:怎么让Mypy明白,当a是FalsyType实例时,if a的条件会判定为假,根本不会执行append语句?
目前虽然可以通过isinstance检查来规避问题,但如果存在多个类似的假值类型,这种写法不仅容易遗漏,还会让代码显得冗余且不够直观:
if not isinstance(a, FalsyType): mylist.append(a)
解决方案:自定义类型守卫函数
我们可以利用Mypy支持的自定义类型守卫(Type Guard),明确告诉类型检查器:当if a的条件为真时,a的类型一定是str。
写一个简单的类型守卫函数,结合TypeGuard标注来实现类型窄化:
import typing as t from typing import TypeGuard class FalsyType: def __bool__(self) -> t.Literal[False]: return False Falsy = FalsyType() def is_truthy_string(value: t.Union[FalsyType, str]) -> TypeGuard[str]: return bool(value) a: t.Union[FalsyType, str] a = Falsy mylist: t.List[str] = [] if is_truthy_string(a): mylist.append(a) # Mypy现在能正确识别这里a是str类型,不再报错 a = "Hello" if is_truthy_string(a): mylist.append(a)
这个类型守卫函数的返回值用TypeGuard[str]标注,意思是:如果函数返回True,那么传入的value必然是str类型。这样Mypy就能准确推断,进入if分支的a不可能是FalsyType实例,自然不会再抛出类型不兼容的错误。
如果后续新增其他假值类型,只需要把类型守卫的参数类型改成对应的联合类型(比如t.Union[FalsyType, AnotherFalsyType, str]),函数逻辑保持return bool(value)即可,扩展性很强。
内容的提问来源于stack exchange,提问作者Sam vL

