List类型标注用Union遇兼容问题,求多类型列表正确类型提示
问题重现
以下代码运行mypy时会触发两个类型错误:
from typing import Union, List def homogenize(l: List[Union[str, int]]) -> List[str]: return [str(i) for i in l] x = [0, 5] homogenize(x) # <-- 错误:`List[int]` 与 `List[Union[str, int]]` 不兼容 y = [0, 'cat'] homogenize(y) # <-- 错误:`List[object]` 与 `List[Union[str, int]]` 不兼容
报错细节:
- 变量
x类型为List[int],无法匹配参数标注的List[Union[str, int]] - 变量
y被自动推断为List[object],同样不符合参数类型要求
核心疑问:函数本应接收包含int或str的列表,为何纯int列表不被允许?如何正确标注这类列表的类型?
原因分析
Python的List类型是**不变(invariant)**的,即只有当两个列表的元素类型完全一致时,它们的类型才兼容。哪怕int是Union[str, int]的子集,List[int]也不能被视为List[Union[str, int]]的子类——这是为了类型安全:如果允许此类赋值,函数理论上可以往列表中添加str元素,而原List[int]根本无法存储str,会引发类型混乱。
另外,y = [0, 'cat']被推断为List[object],是因为Python默认会将包含多种不同类型元素的列表,推断为所有元素的最宽泛公共父类型(此处为object),而非期望的Union[str, int]。
解决方案
1. 用协变迭代类型替代List
由于函数仅需遍历读取元素、无需修改列表,可使用Iterable[Union[str, int]]或Sequence[Union[str, int]]作为参数类型。这两种类型是协变的,允许传入元素类型为Union[str, int]子集的容器:
from typing import Union, Iterable def homogenize(l: Iterable[Union[str, int]]) -> List[str]: return [str(i) for i in l] x = [0, 5] homogenize(x) # 类型检查通过 y: List[Union[str, int]] = [0, 'cat'] homogenize(y) # 类型检查通过
2. 显式标注混合类型列表的类型
对于y这类混合元素列表,需显式标注其类型为List[Union[str, int]],避免Python自动推断为List[object]:
y: List[Union[str, int]] = [0, 'cat']
3. 使用泛型提升灵活性
如果希望函数能处理任意可转换为字符串的类型,可结合泛型实现更灵活的约束:
from typing import Iterable, TypeVar StrCastable = TypeVar('StrCastable', str, int) def homogenize(l: Iterable[StrCastable]) -> List[str]: return [str(i) for i in l]
该方案既保证类型安全,又允许函数接收List[int]、List[str]或List[Union[str, int]]类型的参数。
内容的提问来源于stack exchange,提问作者darda

