T = TypeVar("T", bound=Union[str, int])
def connect_lists(list_1: list[T], list_2: list[T]) -> list[T]:
out: list[T] = []
out.extend(list_1)
out.extend(list_2)
return out
connect_lists([1, 2], ["a", "b"])
mypy:
error: Cannot infer type argument 1 of "connect_lists" [misc]
T = TypeVar("T", bound=Union[str, int])
def connect_lists(list_1: Iterable[T], list_2: Iterable[T]) -> list[T]:
out: list[T] = []
out.extend(list_1)
out.extend(list_2)
return out
connect_lists([1, 2], ["a", "b"])
Now mypy doesn't raise an error.
What is the difference between List and Iterable in this case?
Iterable is covariant - an Iterable[int] is also an Iterable[int|str].
list is not covariant - a list[int] is not a list[int|str], because you can add strings to a list[int|str], which you can't do with a list[int].
mypy infers the types of [1, 2] and ["a", "b"] as list[int] and list[str] respectively. With the first definition of connect_objects, there is no choice of T that will make the call valid. But with the second definition, a list[int] is an Iterable[int], which is an Iterable[int|str], and a list[str] is similarly also an Iterable[int|str], so T is inferred as int|str.
I don't think there's actually a spec yet for how type inference works. There may never be such a spec. Future mypy versions might perform this inference differently, for example, performing context-sensitive inference to infer a type of list[int|str] for both input lists, making the first version of the code pass type checking.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With