Python Typing
On this page
Typing
The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.
Type aliases
A type alias is defined using the type
statement, which creates an instance of TypeAliasType
. In this example, Vector
and list[float]
will be treated equivalently by static type checkers:
type Vector = list[float]def scale(scalar: float, vector: Vector) -> Vector:return [scalar * num for num in vector]# passes type checking; a list of floats qualifies as a Vector.new_vector = scale(2.0, [1.0, -4.2, 5.4])
Before Python 3.12, you marked with TypeAlias
to make it explicit that this is a type alias, not a normal variable assignment:
from typing import TypeAliasVector: TypeAlias = list[float]
NewType
Use the NewType
helper to create distinct types:
from typing import NewTypeUserId = NewType('UserId', int)some_id = UserId(524313)
You may still perform all int
operations on a variable of type UserId
, but the result will always be of type int
. This lets you pass in a UserId
wherever an int
might be expected, but will prevent you from accidentally creating a UserId
in an invalid way:
# 'output' is of type 'int', not 'UserId'output = UserId(23413) + UserId(54341)
It is possible to create a NewType
based on a ‘derived’ NewType
:
from typing import NewTypeUserId = NewType('UserId', int)ProUserId = NewType('ProUserId', UserId)
Annotating callable objects
Functions – or other callable objects – can be annotated using collections.abc.Callable
or deprecated typing.Callable
. Callable[[int], str]
signifies a function that takes a single parameter of type int
and returns a str
.
from collections.abc import Callable, Awaitabledef feeder(get_next_item: Callable[[], str]) -> None:... # Bodydef async_query(on_success: Callable[[int], None],on_error: Callable[[int, Exception], None]) -> None:... # Bodyasync def on_update(value: str) -> None:... # Bodycallback: Callable[[str], Awaitable[None]] = on_update
Generics
Since type information about objects kept in containers cannot be statically inferred in a generic way, many container classes in the standard library support subscription to denote the expected types of container elements.
from collections.abc import Mapping, Sequenceclass Employee: ...# Sequence[Employee] indicates that all elements in the sequence# must be instances of "Employee".# Mapping[str, str] indicates that all keys and all values in the mapping# must be strings.def notify_by_email(employees: Sequence[Employee],overrides: Mapping[str, str]) -> None: ...
Generic functions and classes can be parameterized by using type parameter syntax:
from collections.abc import Sequencedef first[T](l: Sequence[T]) -> T: # Function is generic over the TypeVar "T"return l[0]
Or by using the TypeVar
factory directly:
from collections.abc import Sequencefrom typing import TypeVarU = TypeVar('U') # Declare type variable "U"def second(l: Sequence[U]) -> U: # Function is generic over the TypeVar "U"return l[1]
Annotating tuples
For most containers in Python, the typing system assumes that all elements in the container will be of the same type.
from collections.abc import Mapping# Type checker will infer that all elements in ``x`` are meant to be intsx: list[int] = []# Type checker error: ``list`` only accepts a single type argument:y: list[int, str] = [1, 'foo']# Type checker will infer that all keys in ``z`` are meant to be strings,# and that all values in ``z`` are meant to be either strings or intsz: Mapping[str, str | int] = {}
To denote a tuple which could be of any length, and in which all elements are of the same type T
, use tuple[T, ...]
. To denote an empty tuple, use tuple[()]
. Using plain tuple
as an annotation is equivalent to using tuple[Any, ...]
:
x: tuple[int, ...] = (1, 2)# These reassignments are OK: ``tuple[int, ...]`` indicates x can be of any lengthx = (1, 2, 3)x = ()# This reassignment is an error: all elements in ``x`` must be intsx = ("foo", "bar")# ``y`` can only ever be assigned to an empty tupley: tuple[()] = ()z: tuple = ("foo", "bar")# These reassignments are OK: plain ``tuple`` is equivalent to ``tuple[Any, ...]``z = (1, 2, 3)z = ()
The type of class objects
A variable annotated with C
may accept a value of type C
. In contrast, a variable annotated with type[C]
(or deprecated typing.Type[C]
) may accept values that are classes themselves – specifically, it will accept the class object of C
.
class User: ...class ProUser(User): ...class TeamUser(User): ...def make_new_user(user_class: type[User]) -> User:# ...return user_class()make_new_user(User) # OKmake_new_user(ProUser) # Also OK: ``type[ProUser]`` is a subtype of ``type[User]``make_new_user(TeamUser) # Still finemake_new_user(User()) # Error: expected ``type[User]`` but got ``User``make_new_user(int) # Error: ``type[int]`` is not a subtype of ``type[User]``
Annotating generators and coroutines
A generator can be annotated using the generic type Generator[YieldType, SendType, ReturnType]
.
def echo_round() -> Generator[int, float, str]:sent = yield 0while sent >= 0:sent = yield round(sent)return 'Done'
The SendType
and ReturnType
parameters default to None
:
def infinite_stream(start: int) -> Generator[int]: # or Generator[int, None, None]while True:yield startstart += 1
Simple generators that only ever yield values can also be annotated as having a return type of either Iterable[YieldType]
or Iterator[YieldType]
:
def infinite_stream(start: int) -> Iterator[int]:while True:yield startstart += 1
Async generators are handled in a similar fashion, but don’t expect a ReturnType
type argument (AsyncGenerator[YieldType, SendType]
). The SendType
argument defaults to None
, so the following definitions are equivalent:
async def infinite_stream(start: int) -> AsyncGenerator[int]:while True:yield startstart = await increment(start)async def infinite_stream(start: int) -> AsyncGenerator[int, None]:while True:yield startstart = await increment(start)
User-defined generic types
A user-defined class can be defined as a generic class.
from logging import Loggerclass LoggedVar[T]:def __init__(self, value: T, name: str, logger: Logger) -> None:self.name = nameself.logger = loggerself.value = valuedef set(self, new: T) -> None:self.log('Set ' + repr(self.value))self.value = newdef get(self) -> T:self.log('Get ' + repr(self.value))return self.valuedef log(self, message: str) -> None:self.logger.info('%s: %s', self.name, message)
This syntax indicates that the class LoggedVar
is parameterised around a single type variable T
. This also makes T
valid as a type within the class body.
Generic classes implicitly inherit from Generic
. For compatibility with Python 3.11 and lower, it is also possible to inherit explicitly from Generic
to indicate a generic class:
from typing import TypeVar, GenericT = TypeVar('T')class LoggedVar(Generic[T]):...
Generic classes have __class_getitem__()
methods, meaning they can be parameterised at runtime (e.g. LoggedVar[int]
below):
from collections.abc import Iterabledef zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:for var in vars:var.set(0)
A generic type can have any number of type variables. All varieties of TypeVar
are permissible as parameters for a generic type:
from typing import TypeVar, Generic, Sequenceclass WeirdTrio[T, B: Sequence[bytes], S: (int, str)]:...OldT = TypeVar('OldT', contravariant=True)OldB = TypeVar('OldB', bound=Sequence[bytes], covariant=True)OldS = TypeVar('OldS', int, str)class OldWeirdTrio(Generic[OldT, OldB, OldS]):...
The Any
type
A special kind of type is Any
. A static type checker will treat every type as being compatible with Any
and Any
as being compatible with every type.
from typing import Anya: Any = Nonea = [] # OKa = 2 # OKs: str = ''s = a # OKdef foo(item: Any) -> int:# Passes type checking; 'item' could be any type,# and that type might have a 'bar' methoditem.bar()...