Skip to content
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

Consider special casing zip(*tuples) #5247

Open
JukkaL opened this issue Jun 20, 2018 · 7 comments
Open

Consider special casing zip(*tuples) #5247

JukkaL opened this issue Jun 20, 2018 · 7 comments
Labels
feature priority-1-normal topic-calls Function calls, *args, **kwargs, defaults topic-plugins The plugin API and ideas for new plugins

Comments

@JukkaL
Copy link
Collaborator

JukkaL commented Jun 20, 2018

This seems to be a somewhat common zip idiom:

>>> a = [('a', 1), ('b', 2), ('c', 3)]
>>> list(zip(*a))
[('a', 'b', 'c'), (1, 2, 3)]

The inferred type for zip(*a) is Iterator[Tuple[Any, ...]], which causes false negatives in multiple assignments like this:

x, y = zip(*a)

Maybe we could add a plugin feature that can provide more precise types for zip(*a) in a multiple assignment context somehow. For example, the inferred type in the above example could be Tuple[Tuple[str, ...], Tuple[int, ...]] instead of Iterator[...], but only in the context of a multiple assignment since elsewhere the inferred type is not safe to use.

@ilevkivskyi ilevkivskyi added feature priority-1-normal topic-plugins The plugin API and ideas for new plugins labels Jun 20, 2018
@gvanrossum
Copy link
Member

gvanrossum commented Jun 20, 2018 via email

@JukkaL
Copy link
Collaborator Author

JukkaL commented Jun 20, 2018

In this case we'd need an iterator over a tuple of items. For example, TupleIterator[int, str] could be an iterator that can be iterated exactly twice, and the first item will be an integer and the second one a string. I'm skeptical about this being useful enough to be worth adding as a type system extension, since any new type requires a substantial amount of work and the complexity of the type system grows in a non-linear fashion. It might be enough to add ad hoc support for the few most common use cases through plugins.

@gvanrossum
Copy link
Member

gvanrossum commented Jun 20, 2018 via email

@elazarg
Copy link
Contributor

elazarg commented Jun 22, 2018

Why isn't it a TupleType? I think that the concept of TupleType should have little to do with tuples specifically, they are just a class that we know should behave like one (and this is why we have a fallback, right? it's the intersection, so this iterator is the intersection of the iterator protocol and a TupleType)

@JukkaL
Copy link
Collaborator Author

JukkaL commented Jun 22, 2018

@elazarg An interesting idea! It's not strictly a tuple type since tuples support indexing with integers but this doesn't work with an iterator. However, we could perhaps add an extra check so that TupleType indexing only works if the fallback provides __getitem__. TupleType would mean "supports tuple interface for all methods provided by the fallback type". We'd need to do this for each special case operation supported for TupleType, which would complicate things a bit.

This would still need a plugin since there's no syntax for defining a type that is tuple-like but doesn't extend the tuple class.

@JelleZijlstra JelleZijlstra added the topic-calls Function calls, *args, **kwargs, defaults label Mar 19, 2022
@CarliJoy
Copy link

CarliJoy commented Jan 9, 2024

Any news on this?

For the moment I helped myself with this (limited) function.
I tried TypeVarTuple but couldn't get it to work.

from typing import TypeVar, Iterable

T1 = TypeVar("T1")
T2 = TypeVar("T2")
T3 = TypeVar("T3")
T4 = TypeVar("T4")
T5 = TypeVar("T5")


@overload
def transpose(
    iterable: Iterable[tuple[T1, T2, T3, T4, T5]], strict: bool = False
) -> tuple[Iterable[T1], Iterable[T2], Iterable[T3], Iterable[T4], Iterable[T5]]:
    ...


@overload
def transpose(
    iterable: Iterable[tuple[T1, T2, T3, T4]], strict: bool = False
) -> tuple[Iterable[T1], Iterable[T2], Iterable[T3], Iterable[T4]]:
    ...


@overload
def transpose(
    iterable: Iterable[tuple[T1, T2, T3]], strict: bool = False
) -> tuple[Iterable[T1], Iterable[T2], Iterable[T3]]:
    ...


@overload
def transpose(
    iterable: Iterable[tuple[T1, T2]], strict: bool = False
) -> tuple[Iterable[T1], Iterable[T2]]:
    ...


def transpose(
    iterable: (
        Iterable[tuple[T1, T2]]
        | Iterable[tuple[T1, T2, T3]]
        | Iterable[tuple[T1, T2, T3, T4]]
        | Iterable[tuple[T1, T2, T3, T4, T5]]
    ),
    strict: bool = False,
) -> (
    tuple[Iterable[T1], Iterable[T2]]
    | tuple[Iterable[T1], Iterable[T2], Iterable[T3]]
    | tuple[Iterable[T1], Iterable[T2], Iterable[T3], Iterable[T4]]
    | tuple[Iterable[T1], Iterable[T2], Iterable[T3], Iterable[T4], Iterable[T5]]
):
    """
    Transpose the elements of given iterable, type safe

    Only a typed shortcut for zip(*iterable)
    See https://github.com/python/mypy/issues/5247 for background
    """
    return zip(*iterable, strict=strict)  # type: ignore

Cross posted at stackoverflow

@finite-state-machine
Copy link

In case it's helpful to have a gist for this: mypy-play.net

from __future__ import annotations
from typing_extensions import *

def is_even(value: int) -> bool:
    return not (value % 2)

table = [(i, is_even(i), str(i)) for i in range(20)]
assert_type(table, List[Tuple[int, bool, str]])

numbers, evens, strs = zip(*table)

#                         we get...         we hoped for...
#                         ───────────────   ────────────────
reveal_type(numbers)    # Tuple[Any, ...]   Tuple[int, ...]
reveal_type(evens)      # Tuple[Any, ...]   Tuple[bool, ...]
reveal_type(strs)       # Tuple[Any, ...]   Tuple[str, ...]

# these statements hold true at runtime:
assert numbers == tuple(range(20))
assert evens == tuple(is_even(i) for i in range(20))
assert strs == tuple(str(i) for i in range(20))

# these all fail today:
assert_type(numbers, Tuple[int, ...])
assert_type(numbers, Tuple[bool, ...])
assert_type(numbers, Tuple[str, ...])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature priority-1-normal topic-calls Function calls, *args, **kwargs, defaults topic-plugins The plugin API and ideas for new plugins
Projects
None yet
Development

No branches or pull requests

7 participants