在使用 MyPy 对 Python 项目做静态类型检查时,函数返回联合类型往往会带来赋值不兼容的错误,这类问题会影响类型检查的准确性,也会让代码的逻辑可读性下降。下面我们就来详细分析这类问题的成因和解决方法。

联合类型的基本定义
联合类型指的是一个值可以是多种类型中的一种,在 Python 中可以通过 typing.Union 来定义,Python 3.10 及以上版本还可以使用 int | str 这样的语法来声明。比如下面的函数就返回 int 或者 str 类型的联合类型:
from typing import Union
def get_value(flag: bool) -> Union[int, str]:
if flag:
return 10
else:
return "hello"
赋值不兼容错误的常见场景
当我们把返回联合类型的函数结果赋值给一个类型范围更小的变量时,MyPy 就会报出赋值不兼容的错误。比如下面的代码就会出现这类问题:
from typing import Union
def get_value(flag: bool) -> Union[int, str]:
if flag:
return 10
else:
return "hello"
# 声明变量为 int 类型,但是函数返回的是 int 或 str 的联合类型
num: int = get_value(True)
MyPy 会提示错误:Incompatible types in assignment (expression has type "int | str", variable has type "int"),原因是联合类型 int | str 的范围比单独的 int 类型更大,直接赋值不符合类型规则。
解决方法
1. 对联合类型做类型收窄
最常见的解决方式是通过条件判断对联合类型的返回值做类型收窄,让 MyPy 能够明确当前返回值的实际类型。比如上面的代码可以修改为:
from typing import Union
def get_value(flag: bool) -> Union[int, str]:
if flag:
return 10
else:
return "hello"
result = get_value(True)
if isinstance(result, int):
# 这里 MyPy 已经知道 result 是 int 类型,可以赋值给 int 变量
num: int = result
print(num + 1)
elif isinstance(result, str):
print(result.upper())
2. 调整变量类型注解匹配联合类型
如果不需要对返回值做类型区分,也可以直接把变量的类型注解调整为和函数返回类型一致的联合类型,避免类型不匹配:
from typing import Union
def get_value(flag: bool) -> Union[int, str]:
if flag:
return 10
else:
return "hello"
# 变量类型注解和函数返回类型一致
value: Union[int, str] = get_value(True)
print(value)
3. 使用类型断言(谨慎使用)
如果开发者明确知道当前返回值的具体类型,也可以使用类型断言来告诉 MyPy 实际的类型,但是这种方式需要开发者自己保证类型正确,否则运行时可能出现错误:
from typing import Union
def get_value(flag: bool) -> Union[int, str]:
if flag:
return 10
else:
return "hello"
# 类型断言,明确告诉 MyPy 返回值是 int 类型
num: int = get_value(True) # type: ignore[assignment]
# 或者使用 typing.cast
from typing import cast
num = cast(int, get_value(True))
print(num + 1)
注意事项
在使用联合类型的时候,尽量避免不必要的联合类型声明,比如函数实际只会返回一种类型的时候,不要声明为联合类型。另外类型收窄的时候尽量使用 isinstance 这类 MyPy 能够识别的方式,不要使用过于复杂的条件判断,否则 MyPy 可能无法正确收窄类型。如果项目中大量使用联合类型,建议统一联合类型的声明方式,比如统一使用 Python 3.10+ 的 | 语法,或者统一使用 Union,避免风格混乱。