Reputation: 9620
Why in the following does test
not typecheck (with 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))
The error message:
error: Incompatible types in assignment (expression has type "Tuple[Union[_T, int], ...]", variable has type "Tuple[int, int, int]")
Observation: switching to Tuple[int, ...]
removes the error.
Comment: I find this much more surprising than the failure of test: Tuple[int,int,int] = tuple(range(3))
to typecheck (with a more understandable error message), but perhaps it is the same underlying issue: length inference.
EDIT:
In response to @MisterMiyagi's second comment, consider the following, which raises the same error but has clearly inferable length:
xss: Tuple[Tuple[int,int,int], ...] = tuple((1,2,3) for _ in range(10))
test: Tuple[int,int,int] = tuple(map(sum, zip(*xss)))
Upvotes: 2
Views: 698
Reputation: 50086
Python's type system does not generally express item count. Tuple
is the only exception; using any type but Tuple
cannot express item count and thus discards such information. In short, there is no length to infer because Python's type system does not know such a thing.
In specific, both map
and zip
resolve to an Iterator[...]
(e.g. signature for map). Any previous item count information is discarded by this. The intermediate type translations can be checked with mypy's reveal_type
:
xss: Iterator[Tuple[int,int,int]] = ((1,2,3) for _ in range(10))
test: Tuple[int,int,int] = tuple(map(sum, zip(*xss)))
reveal_type(zip(*xss)) # note: Revealed type is 'typing.Iterator[builtins.tuple[Any]]'
reveal_type(map(sum, *xss)) # note: Revealed type is 'typing.Iterator[Union[_T`-1, builtins.int]]'
While one could in principle propagate item counts across iterators, this is not reliable in general with static type checking. The underlying problem is that an arbitrary iterator is single-use: an "n
size Iterator
" becomes an "n-1
size Iterator
" after one iteration step and a "0
size Iterator
" after exhaustion.
Therefore, the type of a variable holding an "n
size Iterator
" would have to change depending on use.
a: Iterator[int] = iter((1, 2))
# a is "2 size Iterator"
b = tuple(a)
# a is "0 size Iterator"
c = tuple(a)
Upvotes: 1