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).
Related
I have two variables a and b that are either int or str.
I write an assertion that insists a and b are either both ints or strs.
If I typenarrow a to an int, is there a way for mypy to infer b is also an int?
Here is some sample code.
Mypy version:
mypy 0.980+dev.0f17aff06ac1c05c442ba989e23655a2c6adbfbf (compiled: no)
Thanks for your help.
def my_func(a: int | str, b: int | str):
# We assert one of the statements is true: 1) a and b are ints, or 2) a and b are strings.
# In the case of an int a, and a string b, or vice versa, this assertion will fail.
assert isinstance(a, int) == isinstance(b, int)
# Another alternative assertion
assert type(a) == type(b)
if isinstance(a, int):
reveal_type(b) # mypy still thinks b is int or str
Using typing.TypeVar:
from typing import TypeVar
T = TypeVar('T', int, str)
def reveal_type(a: int):
pass
def foo(a: str):
pass
def my_func(a: T, b: T):
if isinstance(a, int):
reveal_type(b) # pass
else:
foo(b) # pass
If we simply exchange the calling positions of the two functions, mypy will find that they are all wrong calls and give two errors:
def my_func(a: T, b: T):
if isinstance(a, int):
foo(b) # Argument 1 to "foo" has incompatible type "int"; expected "str" (16:12)
else:
reveal_type(b) # Argument 1 to "reveal_type" has incompatible type "str"; expected "int" (18:20)
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)
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
Type-checking the following code with mypy:
def foo(a: str, b: float, c: int):
print(a, b, c + 1)
foo('ok', 2.2, 'bad')
reveals the invalid call too foo with:
error: Argument 3 to "foo" has incompatible type "str"; expected "int"
Now let's say we have a wrapper function like the following:
from typing import Callable, Any
def say_hi_and_call(func: Callable[..., Any], *args):
print('Hi.')
func(*args)
and do an invalid call using it
say_hi_and_call(foo, 'ok', 2.2, 'bad')
mypy will not report any errors, instead we will only get to know about this error at runtime:
TypeError: must be str, not int
I'd like to catch this error earlier. Is there a possibility to refine the type annotations in a way that mypy is able to report the problem?
OK, the only solution I came up with is making the arity of the function explicit, i.e.
from typing import Any, Callable, TypeVar
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
def say_hi_and_call_ternary(func: Callable[[A, B, C], Any], a: A, b: B, c: C):
print('Hi.')
func(a, b, c)
def foo(a: str, b: float, c: int):
print(a, b, c + 1)
say_hi_and_call_ternary(foo, 'ok', 2.2, 'bad')
Of course one would need a similar say_hi_and_call_unary and say_hi_and_call_binary etc. too.
But since I value my application not exploding in PROD over saving some LOC, I'm happy when mypy is able to report the error, which now certainly is the case:
error: Argument 1 to "say_hi_and_call_ternary" has incompatible type "Callable[[str, float, int], Any]"; expected "Callable[[str, float, str], Any]"