Craig
Craig

Reputation: 811

What does typing.cast do in Python?

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

Answers (2)

Arthur Tacca
Arthur Tacca

Reputation: 9989

There's context missing from the other answer that you need to understand before the answer to this question makes sense:

What is static typing?

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" 

What is the use of 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

Zev Chonoles
Zev Chonoles

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

Related Questions