Reputation: 811
import typing
type(typing.cast(int, '11'))
still returns <class 'str'>
instead of int
. Then, what does typing.cast
do here?
Upvotes: 63
Views: 45041
Reputation: 9989
There's context missing from the other answer that you need to understand before the answer to this question makes sense:
Python supports type hints, but they are ignored when you run the program. (Technically it is possible to inspect the type hints at runtime, but this is very rarely used.) For example, the following program will run without error even though print_int
is being passed a str
not an int
:
def print_int(x: int) -> None:
print("The int is:", x)
print_int("seven")
Usually, in practice, if a function type hint is present then the function won't work with other types of classes (unlike the above print_int()
which does actually work for non-int
objects). But to find this out you still have to run the program, which is annoying because it could be that this function is rarely called, or the argument type is only wrong sometimes, or it takes a long time to run, etc.
However, it is possible to run a separate program, called a type checker, that will check that the type hints match up. The two commonly used ones are mypy and pyright. These will run over your codebase and check all annotated functions, classes and variables. For example, with the above program, mypy will say:
error: Argument of type "Literal['seven']" cannot be assigned to parameter "x" of type "int" in function "print_int"
Notably, the type checkers do not run your program – they just check the types. This means they run quickly and detect type errors in all code paths. For example, they will detect the error in the following snippet, even though the code path with an error is unlikely at run time:
from random import randrange
y = randrange(1000000)
z = "seven" if y == 7 else y
print_int(z)
For this snippet, pyright will give the error:
error: Argument of type "int | Literal['seven']" cannot be assigned to parameter "x" of type "int" in function "print_int"
typing.cast
?Now we can finally answer the original question. Consider the following variant of the example code:
y = randrange(1000000)
z = "seven" if y == 7 else y
if y != 7:
print_int(z)
This code will only ever pass an int
to print_int
at runtime. However, the convoluted way this code is written is a bit too clever for the type checkers. All they know is that z
is of type int | str
, and it's being passed to something that needs an int, so they still give an error. So this is where we can help it out with typing.cast
:
y = randrange(1000000)
z = "seven" if y == 7 else y
if y != 7:
print_int(typing.cast(int, z))
As the docs and the other answer notes, this does not change what happens at runtime at all. However, it does silence the type checkers (if we ever run one) because it assures them that the argument being passed to print_int()
really is an int, so no error is generated.
Beware that it is a blunt tool: by definition, it overrides type checking, so it's possible to use it when the types are not actually right. For example, this would pass type checkers with no error:
y = randrange(1000000)
z = "seven" if y == 7 else y
print_int(typing.cast(int, z)) # Oops
Upvotes: 7
Reputation: 1283
From the documentation (emphasis mine):
Cast a value to a type.
This returns the value unchanged. To the type checker this signals that the return value has the designated type, but at runtime we intentionally don’t check anything (we want this to be as fast as possible).
The "casting" only takes place in the type-checking system, not at runtime.
Upvotes: 60