为什么下面
test
不进行类型检查(使用Mypy 0.780)?
from typing import Iterator, Tuple
xs: Iterator[int] = (i for i in (1,2,3))
ys: Iterator[int] = (i for i in (1,2,3))
xys: Iterator[Tuple[int,int]] = zip(*(xs,ys))
test: Tuple[int,int,int] = tuple(map(sum, xys))
错误消息:
错误:赋值中的类型不兼容(表达式的类型为“Tuple[Union[_T, int], ...] ", 变量的类型为 "Tuple[int, int, int]")
观察:切换到
Tuple[int, ...]
会消除错误。
评论:我发现这比
test: Tuple[int,int,int] = tuple(range(3))
类型检查失败更令人惊讶(带有更容易理解的错误消息),但也许是相同的根本问题:长度推断。
编辑:
为了回应@MisterMiyagi的第二条评论,请考虑以下内容,它会引发相同的错误,但显然可以推断长度:
xss: Tuple[Tuple[int,int,int], ...] = tuple((1,2,3) for _ in range(10))
test: Tuple[int,int,int] = tuple(map(sum, zip(*xss)))
Mypy 的错误是由于它无法推断出
zip(*xss)
的结果将始终产生一个长度为 3 的迭代器。让我们逐步分解一下:
-
xss
的类型:xss
被正确推断为Tuple[Tuple[int, int, int], ...]
,表示它是一个包含长度可变的元组的元组,其中每个元组都包含三个整数。 -
zip(*xss)
:*xss
语法解包了xss
元组,将其作为单独的参数传递给zip()
。由于xss
中的元组数量是未知的(尽管我们知道每个元组都有 3 个元素),zip()
将生成一个长度未知的迭代器。 -
map(sum, zip(*xss))
: 这将对zip(*xss)
生成的每个元组应用sum()
函数。由于元组的长度未知,sum()
的结果类型被推断为Union[_T, int]
,其中_T
是sum()
中第一个参数的类型(在这种情况下,它是未知的)。 -
tuple(...)
: 这将迭代器的结果转换为元组。但是,由于迭代器长度未知,结果元组的类型变为Tuple[Union[_T, int], ...]
,表示它是一个包含长度可变的元素的元组,其中每个元素可以是_T
类型或int
类型。 -
类型不匹配: 最后,将此元组赋值给
test
,其类型为Tuple[int, int, int]
。由于Tuple[Union[_T, int], ...]
与Tuple[int, int, int]
不兼容(前者可能更长或包含不同类型的元素),因此 Mypy 会引发错误。
解决方案:
如所述,将
test
的类型更改为
Tuple[int, ...]
可以解决该错误,因为它允许元组具有任意长度。
另一种解决方案是使用类型提示来帮助 Mypy 推断长度。例如,可以使用
cast()
函数将
zip(*xss)
的结果转换为特定长度的迭代器:
from typing import cast, Iterator, Tuple
xss: Tuple[Tuple[int, int, int], ...] = tuple((1, 2, 3) for _ in range(10))
test: Tuple[int, int, int] = tuple(map(sum, cast(Iterator[Tuple[int, int, int]], zip(*xss))))
这告诉 Mypy,
zip(*xss)
的结果实际上是一个长度为 3 的迭代器,从而允许它正确地推断出
test
的类型。