Skip to content

Failure to correctly infer tuple type through zip #8454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
JonathanShor opened this issue Feb 28, 2020 · 4 comments · Fixed by python/typeshed#3830
Closed

Failure to correctly infer tuple type through zip #8454

JonathanShor opened this issue Feb 28, 2020 · 4 comments · Fixed by python/typeshed#3830

Comments

@JonathanShor
Copy link

The following code runs correctly on Python 3.7.3:

vals = (("zz", 1), ("yyy", 8))
EXAMPLE: Dict[str, List[Union[str, int]]] = { k: list(val) for k, val in zip(["A", "B"], zip(*vals)) }

producing
>>> EXAMPLE {'A': ['zz', 'yyy'], 'B': [1, 8]}

but raises the following error with mypy 0.761 (and on https://mypy-play.net/):

error: Argument 1 to "list" has incompatible type "Tuple[object, object]"; expected "Iterable[Union[str, int]]"

Explicitly typing the tuple as follows removes the error:
vals: Tuple[Tuple[str, int], ...] = (("zz", 1), ("yyy", 8))

Interesting, explicitly typing the tuple and the nested tuples does not remove the error:
vals: Tuple[Tuple[str, int], Tuple[str, int]] = (("zz", 1), ("yyy", 8))
produces the same error above when no type hint is provided.

Possibly related to #4975 ?

@JonathanShor
Copy link
Author

Might be relevant, but when vals is 6 or more entries long, the error is not produced. Mypy is fine with the following.

vals = (("zz", 1), ("yyy", 2), ("xxx", 3), ("w", 4), ("v", 5), ("u", 6))
EXAMPLE: Dict[str, List[Union[str, int]]] = { k: list(val) for k, val in zip(["A", "B"], zip(*vals)) }
...
>>> EXAMPLE
{'A': ['zz', 'yyy', 'xxx', 'w', 'v', 'u'], 'B': [1, 2, 3, 4, 5, 6]}

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Mar 1, 2020

This is basically because of how zip is typed in typeshed: https://github.com/python/typeshed/blob/master/stdlib/2and3/builtins.pyi#L1446

As you can see, it treats its input arguments as Iterable[T] (a homogenous iterable). When mypy tries to figure out what the most appropriate T for Tuple[str, int] is, it correctly infers object (the common baseclass of str and int). Hence:

~/delete λ cat test37.py  
vals = (("zz", 1), ("yyy", 8))
reveal_type(vals)
reveal_type(zip(*vals))

~/delete λ mypy test37.py
test37.py:2: note: Revealed type is 'Tuple[Tuple[builtins.str, builtins.int], Tuple[builtins.str, builtins.int]]'
test37.py:3: note: Revealed type is 'typing.Iterator[Tuple[builtins.object*, builtins.object*]]'

You can also see from how zip is typed at the typeshed link that if you pass in enough arguments or splat something of length that's unknown to the type system, it doesn't list out the typevars and instead gives up and returns Any.
"Something of unknown length to the type system" explains why your explicit type annotation works (would also work if you typed it as Iterable or made the thing a list).
"Pass in enough arguments" explains your second comment.

The output of something like zip(*[("a", 1), ("b", 2)]) is an Iterator with the first item Tuple[str] and the second item Tuple[int] and there isn't really a good way to type that.
We could lie in typeshed for a couple of these, maybe with something like def zip(*iter: Tuple[_T1, _T2]) -> Tuple[Tuple[_T1, ...], Tuple[_T2, ...]]

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Mar 1, 2020

So maybe we should move this issue to typeshed and see if people have objections to that lie^

@ilevkivskyi
Copy link
Member

Yes, properly typing this would require heterogeneous iterables, it is unlikely it will be supported in foreseeable future. You can however propose the quick fix (adding few extra overloads) on typeshed tracker, as suggested.

hauntsaninja pushed a commit to hauntsaninja/typeshed that referenced this issue Mar 6, 2020
Technically this is a lie, since we return a heterogeneous iterator, not
a tuple. But since we don't have a way of typing heterogeneous
iterators, this is the best we can do.

Fixes python/mypy#8454
JelleZijlstra pushed a commit to python/typeshed that referenced this issue May 4, 2020
Technically this is a lie, since we return a heterogeneous iterator, not
a tuple. But since we don't have a way of typing heterogeneous
iterators, this is the best we can do.

Fixes python/mypy#8454
vishalkuo pushed a commit to vishalkuo/typeshed that referenced this issue Jun 26, 2020
Technically this is a lie, since we return a heterogeneous iterator, not
a tuple. But since we don't have a way of typing heterogeneous
iterators, this is the best we can do.

Fixes python/mypy#8454
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants