My code is as follows:
from typing import Tuple
a: Tuple[int, int] = tuple(sorted([1, 3]))
Mypy tells me:
Incompatible types in assignment (expression has type "Tuple[int,
...]", variable has type "Tuple[int, int]")
What am I doing wrong? Why can't Mypy figure out that the sorted tuple will give back exactly two integers?
The call to sorted produces a List[int] which carries no information about length. As such, producing a tuple from it also has no information about the length. The number of elements simply is undefined by the types you use.
You must tell your type checker to trust you in such cases. Use # type: ignore or cast to unconditionally accept the target type as valid:
# ignore mismatch by annotation
a: Tuple[int, int] = tuple(sorted([1, 3])) # type: ignore
# ignore mismatch by cast
a = cast(Tuple[int, int], tuple(sorted([1, 3])))
Alternatively, create a length-aware sort:
def sort_pair(a: T, b: T) -> Tuple[T, T]:
return (a, b) if a <= b else (b, a)
Related
The following code gives error for tuple constructor:
def foo() -> tuple[int, int, bool]:
a: int = 1
b: int = 2
c: bool = True
results: tuple[int, int, bool] = tuple((a, b, c)) # mypy fails
results: tuple[int, int, bool] = (a, b, c) # mypy success
results: tuple[int, int, bool] = a, b, c # mypy success
return results
Error as below:
error: Incompatible types in assignment (expression has type "Tuple[object, ...]", variable has type "Tuple[int, int, bool]") [assignment]
edited:
error: Incompatible types in assignment (expression has type "Tuple[int, ...]", variable has type "Tuple[int, int, bool]")
Note, I get a slightly different error:
x.py:6: error: Incompatible types in assignment (expression has type "Tuple[int, ...]", variable has type "Tuple[int, int, bool]") [assignment]
Found 1 error in 1 file (checked 1 source file)
With:
mbp16-2019:~ jarrivillaga$ mypy --version
mypy 0.990 (compiled: yes)
So, you can think of tuple being type-hinted like this (see that actual typeshed hints here:
import typing
T = typing.TypeVar("T")
class tuple(object):
def __new__(cls, data: typing.Iterable[T]) -> tuple[T, ...]:
...
So the type of the literal:
data: tuple[int, int, bool] = (a, b, c)
gets promoted to:
data: tuple[int, ...]
Note, that bool is a subclass of int.
I suspect this is what is happening with your real example, (maybe you aren't using bool but something like str, in which case it has to promote to object).
I'm working with a function that is a bit like this (super simplified, as an example):
def foo(*stuff: None | int):
stuff_not_none = [x for x in stuff if x is not None]
if len(stuff_not_none) is 0:
return None
return sum(stuff_not_none)
If I call the function using:
foo(*[1, 2, 3]), I'd want the return type to be inferred to int.
foo(*[None, None]), I'd want the return type to be inferred to None.
foo(*[1, None]), the dream would be inferred to int, but ok if None | int.
I've tried with generics / overloads, but I couldn't figure out this puzzle. How can I achieve this?
The solution:
from typing import overload
#overload
def foo(*stuff: None) -> None: ... # type: ignore[misc]
#overload
def foo(*stuff: int | None) -> int: ...
def foo(*stuff: int | None) -> int | None:
stuff_not_none = [x for x in stuff if x is not None]
if len(stuff_not_none) is 0:
return None
return sum(stuff_not_none)
reveal_type(foo(None, None)) # revealed type is None
reveal_type(foo(1, 2, 3)) # revealed type is int
reveal_type(foo(None, 2, None, 4)) # revealed type is int
foo('a', 'b') # error: no matching overload
Mypy hates this kind of thing, because the overloads overlap. But you'll find that if you add a type: ignore comment in the right place, it's perfectly able to infer the correct types anyway. (I'm a typeshed maintainer, and we do this kind of thing at typeshed all the time.)
Note that the order of the overloads is very important: type checkers will always try the first overload first, and then, only if that doesn't match, will they try the second overload. This is how we get the int revealed type when we pass in a mixture of ints and Nones: the first overload doesn't match, because of the presence of ints, so the type checker is forced to try the second overload.
Mypy playground demo: https://mypy-play.net/?mypy=latest&python=3.10&gist=ff07808e0a314208fdfa6291dcf9f717
Is it possible to use type hinting when unpacking a tuple? I want to do this, but it results in a SyntaxError:
from typing import Tuple
t: Tuple[int, int] = (1, 2)
a: int, b: int = t
# ^ SyntaxError: invalid syntax
According to PEP-0526, you should annotate the types first, then do the unpacking
a: int
b: int
a, b = t
In my case i use the typing.cast function to type hint an unpack operation.
t: tuple[int, int] = (1, 2)
a, b = t
# type hint of a -> Literal[1]
# type hint of b -> Literal[2]
By using the cast(new_type, old_type) you can cast those ugly literals into integers.
from typing import cast
a, b = cast(tuple[int, int], t)
# type hint of a -> int
# type hint of b -> int
This can be useful while working with Numpy NDArrays with Unknown types
# type hint of arr -> ndarray[Unknown, Unknown]
a, b = cast(tuple[float, float], arr[i, j, :2]
# type hint of a -> float
# type hint of b -> float
Is it possible to use type hinting when unpacking a tuple? I want to do this, but it results in a SyntaxError:
from typing import Tuple
t: Tuple[int, int] = (1, 2)
a: int, b: int = t
# ^ SyntaxError: invalid syntax
According to PEP-0526, you should annotate the types first, then do the unpacking
a: int
b: int
a, b = t
In my case i use the typing.cast function to type hint an unpack operation.
t: tuple[int, int] = (1, 2)
a, b = t
# type hint of a -> Literal[1]
# type hint of b -> Literal[2]
By using the cast(new_type, old_type) you can cast those ugly literals into integers.
from typing import cast
a, b = cast(tuple[int, int], t)
# type hint of a -> int
# type hint of b -> int
This can be useful while working with Numpy NDArrays with Unknown types
# type hint of arr -> ndarray[Unknown, Unknown]
a, b = cast(tuple[float, float], arr[i, j, :2]
# type hint of a -> float
# type hint of b -> float
This isn't my exact use case, but it's similar. Suppose I want to define two typing annotations:
Matrix = np.ndarray
Vector = np.ndarray
Now, I want a potential type-checker to complain when I pass a Matrix to a function that accepts a Vector:
def f(x: Vector):
...
m: Matrix = ...
f(m) # Bad!
How do I mark these types as incompatible?
It appears that I can use typing.NewType to create distinct types:
from typing import NewType
A = NewType('A', int)
B = NewType('B', int)
def f(a: A):
pass
b: B
f(b)
gives
a.py:11: error: Argument 1 to "f" has incompatible type "B"; expected "A"
Unfortunately, it doesn't work with np.ndarray until either numpy implements type hinting or NewType supports a base type of Any.