I don’t use cast
that often. In fact I just realised that I had the arguments the wrong way round in previous posts!
You only need to use it once to slow down a hot path though. Note that if you were to search my code you wouldn’t necessarily find cast
itself because there are alternatives such as Any
, type:ignore
etc. Also how much you might need to use these things depends very much on what you are trying to do.
Someone asked for a concrete example of where you might want to use cast so here is a simplified but not unrealistic example:
from __future__ import annotations
from typing import TypeVar, Generic, Type, Hashable, cast
T = TypeVar('T', bound=Hashable)
class A(Generic[T]):
__slots__ = ("value",)
value: T
# Intern all A instances in this dict so that we can use object.__eq__ and
# object.__hash__ for fast comparison and set/dict operations even when the
# values held by the instances might be arbitrarily complex.
cache: dict[tuple[Type[Hashable], Hashable], Hashable] = {}
def __new__(cls, value: T) -> A[T]:
key = (type(value), value)
cache = cls.cache
try:
return cache[key]
except KeyError:
obj = super().__new__(cls)
obj.value = value
return cache.setdefault(key, obj)
# Here a typechecker can understand the types of the values:
aint = A(1)
astr = A("a")
print(aint.value + 2)
print(astr.value + "b")
If you run mypy on this it will complain about the two return
lines:
$ mypy t.py
t.py: note: In member "__new__" of class "A":
t.py:24:20: error: Incompatible return value type (got "Hashable", expected "A[T]") [return-value]
return cache[key]
^~~~~~~~~~
t.py:28:20: error: Incompatible return value type (got "Hashable", expected "A[T]") [return-value]
return cache.setdefault(key, obj)
^~~~~~~~~~~~~~~~~~~~~~~~~~
Found 2 errors in 1 file (checked 1 source file)
The question is how do you type cache
in such a way that a type checker can understand that if value is of type T
then cache[key]
will return A[T]
as required by the signature of __new__
. The type of value
is part of the key so the type of the looked up dict value is guaranteed at runtime but the type checker can’t understand that.
You could use a cast like:
return cast(A[T], cache[key])
You can also just use type: ignore
:
return cache[key] # type: ignore
The latter has no runtime cost because it is just a comment and I have tested meaningful benchmarks in which the use of cast
gave a measurable slowdown.