Alan
Alan

Reputation: 9620

Why does Mypy produce this puzzling typechecking error?

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

Answers (1)

MisterMiyagi
MisterMiyagi

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

Related Questions