typing — Support for type hints¶
Added in version 3.5.
Source code: Lib/typing.py
Note
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.
This module provides runtime support for type hints.
Consider the function below:
def surface_area_of_cube(edge_length: float) -> str:
return f"The surface area of the cube is {6 * edge_length ** 2}."
The function surface_area_of_cube takes an argument expected to
be an instance of float, as indicated by the type hint
edge_length: float. The function is expected to return an instance
of str, as indicated by the -> str hint.
While type hints can be simple classes like float or str,
they can also be more complex. The typing module provides a vocabulary of
more advanced type hints.
New features are frequently added to the typing module.
The typing_extensions package
provides backports of these new features to older versions of Python.
See also
- Typing cheat sheet
A quick overview of type hints (hosted at the mypy docs)
- Type System Reference section of the mypy docs
The Python typing system is standardised via PEPs, so this reference should broadly apply to most Python type checkers. (Some parts may still be specific to mypy.)
- Static Typing with Python
Type-checker-agnostic documentation written by the community detailing type system features, useful typing related tools and typing best practices.
Specification for the Python Type System¶
The canonical, up-to-date specification of the Python type system can be found at Specification for the Python type system.
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])
Type aliases are useful for simplifying complex type signatures. For example:
from collections.abc import Sequence
type ConnectionOptions = dict[str, str]
type Address = tuple[str, int]
type Server = tuple[Address, ConnectionOptions]
def broadcast_message(message: str, servers: Sequence[Server]) -> None:
...
# The static type checker will treat the previous type signature as
# being exactly equivalent to this one.
def broadcast_message(
message: str,
servers: Sequence[tuple[tuple[str, int], dict[str, str]]]
) -> None:
...
The type statement is new in Python 3.12. For backwards
compatibility, type aliases can also be created through simple assignment:
Vector = list[float]
Or marked with TypeAlias to make it explicit that this is a type alias,
not a normal variable assignment:
from typing import TypeAlias
Vector: TypeAlias = list[float]
NewType¶
Use the NewType helper to create distinct types:
from typing import NewType
UserId = NewType('UserId', int)
some_id = UserId(524313)
The static type checker will treat the new type as if it were a subclass of the original type. This is useful in helping catch logical errors:
def get_user_name(user_id: UserId) -> str:
...
# passes type checking
user_a = get_user_name(UserId(42351))
# fails type checking; an int is not a UserId
user_b = get_user_name(-1)
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)
Note that these checks are enforced only by the static type checker. At runtime,
the statement Derived = NewType('Derived', Base) will make Derived a
callable that immediately returns whatever parameter you pass it. That means
the expression Derived(some_value) does not create a new class or introduce
much overhead beyond that of a regular function call.
More precisely, the expression some_value is Derived(some_value) is always
true at runtime.
It is invalid to create a subtype of Derived:
from typing import NewType
UserId = NewType('UserId', int)
# Fails at runtime and does not pass type checking
class AdminUserId(UserId): pass
However, it is possible to create a NewType based on a ‘derived’ NewType:
from typing import NewType
UserId = NewType('UserId', int)
ProUserId = NewType('ProUserId', UserId)
and typechecking for ProUserId will work as expected.
See PEP 484 for more details.
Note
Recall that the use of a type alias declares two types to be equivalent to
one another. Doing type Alias = Original will make the static type checker
treat Alias as being exactly equivalent to Original in all cases.
This is useful when you want to simplify complex type signatures.
In contrast, NewType declares one type to be a subtype of another.
Doing Derived = NewType('Derived', Original) will make the static type
checker treat Derived as a subclass of Original, which means a
value of type Original cannot be used in places where a value of type
Derived is expected. This is useful when you want to prevent logic
errors with minimal runtime cost.
Added in version 3.5.2.
Changed in version 3.10: NewType is now a class rather than a function. As a result, there is
some additional runtime cost when calling NewType over a regular
function.
Changed in version 3.11: The performance of calling NewType has been restored to its level in
Python 3.9.
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.
For example:
from collections.abc import Callable, Awaitable
def feeder(get_next_item: Callable[[], str]) -> None:
... # Body
def async_query(on_success: Callable[[int], None],
on_error: Callable[[int, Exception], None]) -> None:
... # Body
async def on_update(value: str) -> None:
... # Body
callback: Callable[[str], Awaitable[None]] = on_update
The subscription syntax must always be used with exactly two values: the
argument list and the return type. The argument list must be a list of types,
a ParamSpec, Concatenate, or an ellipsis (...). The return type must
be a single type.
If a literal ellipsis ... is given as the argument list, it indicates that
a callable with any arbitrary parameter list would be acceptable:
def concat(x: str, y: str) -> str:
return x + y
x: Callable[..., str]
x = str # OK
x = concat # Also OK
Callable cannot express complex signatures such as functions that take a
variadic number of arguments, overloaded functions, or
functions that have keyword-only parameters. However, these signatures can be
expressed by defining a Protocol class with a
__call__() method:
from collections.abc import Iterable
from typing import Protocol
class Combiner(Protocol):
def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ...
def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes:
for item in data:
...
def good_cb(*vals: bytes, maxlen: int | None = None) -> list[bytes]:
...
def bad_cb(*vals: bytes, maxitems: int | None) -> list[bytes]:
...
batch_proc([], good_cb) # OK
batch_proc([], bad_cb) # Error! Argument 2 has incompatible type because of
# different name and kind in the callback
Callables which take other callables as arguments may indicate that their
parameter types are dependent on each other using ParamSpec.
Additionally, if that callable adds or removes arguments from other
callables, the Concatenate operator may be used. They
take the form Callable[ParamSpecVariable, ReturnType] and
Callable[Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable], ReturnType]
respectively.
Changed in version 3.10: Callable now supports ParamSpec and Concatenate.
See PEP 612 for more details.
See also
The documentation for ParamSpec and Concatenate provides
examples of usage in Callable.
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, Sequence
class 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 Sequence
def 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 Sequence
from typing import TypeVar
U = TypeVar('U') # Declare type variable "U"
def second(l: Sequence[U]) -> U: # Function is generic over the TypeVar "U"
return l[1]
Changed in version 3.12: Syntactic support for generics is new in Python 3.12.
Annotating tuples¶
For most containers in Python, the typing system assumes that all elements in the container will be of the same type. For example:
from collections.abc import Mapping
# Type checker will infer that all elements in ``x`` are meant to be ints
x: 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 ints
z: Mapping[str, str | int] = {}
list only accepts one type argument, so a type checker would emit an
error on the y assignment above. Similarly,
Mapping only accepts two type arguments: the first
indicates the type of the keys, and the second indicates the type of the
values.
Unlike most other Python containers, however, it is common in idiomatic Python
code for tuples to have elements which are not all of the same type. For this
reason, tuples are special-cased in Python’s typing system. tuple
accepts any number of type arguments:
# OK: ``x`` is assigned to a tuple of length 1 where the sole element is an int
x: tuple[int] = (5,)
# OK: ``y`` is assigned to a tuple of length 2;
# element 1 is an int, element 2 is a str
y: tuple[int, str] = (5, "foo")
# Error: the type annotation indicates a tuple of length 1,
# but ``z`` has been assigned to a tuple of length 3
z: tuple[int] = (1, 2, 3)
To denote a tuple which could be of any length, and in which all elements are
of the same type T, use the literal ellipsis ...: 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 length
x = (1, 2, 3)
x = ()
# This reassignment is an error: all elements in ``x`` must be ints
x = ("foo", "bar")
# ``y`` can only ever be assigned to an empty tuple
y: 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. For
example:
a = 3 # Has type ``int``
b = int # Has type ``type[int]``
c = type(a) # Also has type ``type[int]``
Note that type[C] is covariant:
class User: ...
class ProUser(User): ...
class TeamUser(User): ...
def make_new_user(user_class: type[User]) -> User:
# ...
return user_class()
make_new_user(User) # OK
make_new_user(ProUser) # Also OK: ``type[ProUser]`` is a subtype of ``type[User]``
make_new_user(TeamUser) # Still fine
make_new_user(User()) # Error: expected ``type[User]`` but got ``User``
make_new_user(int) # Error: ``type[int]`` is not a subtype of ``type[User]``
The only legal parameters for type are classes, Any,
type variables, and unions of any of these types.
For example:
def new_non_team_user(user_class: type[BasicUser | ProUser]): ...
new_non_team_user(BasicUser) # OK
new_non_team_user(ProUser) # OK
new_non_team_user(TeamUser) # Error: ``type[TeamUser]`` is not a subtype
# of ``type[BasicUser | ProUser]``
new_non_team_user(User) # Also an error
type[Any] is equivalent to type, which is the root of Python’s
metaclass hierarchy.
Annotating generators and coroutines¶
A generator can be annotated using the generic type
Generator[YieldType, SendType, ReturnType].
For example:
def echo_round() -> Generator[int, float, str]:
sent = yield 0
while sent >= 0:
sent = yield round(sent)
return 'Done'
Note that unlike many other generic classes in the standard library,
the SendType of Generator behaves
contravariantly, not covariantly or invariantly.
The SendType and ReturnType parameters default to None:
def infinite_stream(start: int) -> Generator[int]:
while True:
yield start
start += 1
It is also possible to set these types explicitly:
def infinite_stream(start: int) -> Generator[int, None, None]:
while True:
yield start
start += 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 start
start += 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 start
start = await increment(start)
async def infinite_stream(start: int) -> AsyncGenerator[int, None]:
while True:
yield start
start = await increment(start)
As in the synchronous case,
AsyncIterable[YieldType]
and AsyncIterator[YieldType] are
available as well:
async def infinite_stream(start: int) -> AsyncIterator[int]:
while True:
yield start
start = await increment(start)
Coroutines can be annotated using
Coroutine[YieldType, SendType, ReturnType].
Generic arguments correspond to those of Generator,
for example:
from collections.abc import Coroutine
c: Coroutine[list[str], str, int] # Some coroutine defined elsewhere
x = c.send('hi') # Inferred type of 'x' is list[str]
async def bar() -> None:
y = await c # Inferred type of 'y' is int
User-defined generic types¶
A user-defined class can be defined as a generic class.
from logging import Logger
class LoggedVar[T]:
def __init__(self, value: T, name: str, logger: Logger) -> None:
self.name = name
self.logger = logger
self.value = value
def set(self, new: T) -> None:
self.log('Set ' + repr(self.value))
self.value = new
def get(self) -> T:
self.log('Get ' + repr(self.value))
return self.value
def 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, Generic
T = 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 Iterable
def 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, Sequence
class 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]):
...
Each type variable argument to Generic must be distinct.
This is thus invalid:
from typing import TypeVar, Generic
...
class Pair[M, M]: # SyntaxError
...
T = TypeVar('T')
class Pair(Generic[T, T]): # INVALID
...
Generic classes can also inherit from other classes:
from collections.abc import Sized
class LinkedList[T](Sized):
...
When inheriting from generic classes, some type parameters could be fixed:
from collections.abc import Mapping
class MyDict[T](Mapping[str, T]):
...
In this case MyDict has a single parameter, T.
Using a generic class without specifying type parameters assumes
Any for each position. In the following example, MyIterable is
not generic but implicitly inherits from Iterable[Any]:
from collections.abc import Iterable
class MyIterable(Iterable): # Same as Iterable[Any]
...
User-defined generic type aliases are also supported. Examples:
from collections.abc import Iterable
type Response[S] = Iterable[S] | int
# Return type here is same as Iterable[str] | int
def response(query: str) -> Response[str]:
...
type Vec[T] = Iterable[tuple[T, T]]
def inproduct[T: (int, float, complex)](v: Vec[T]) -> T: # Same as Iterable[tuple[T, T]]
return sum(x*y for x, y in v)
For backward compatibility, generic type aliases can also be created through a simple assignment:
from collections.abc import Iterable
from typing import TypeVar
S = TypeVar("S")
Response = Iterable[S] | int
Changed in version 3.7: Generic no longer has a custom metaclass.
Changed in version 3.12: Syntactic support for generics and type aliases is new in version 3.12.
Previously, generic classes had to explicitly inherit from Generic
or contain a type variable in one of their bases.
User-defined generics for parameter expressions are also supported via parameter
specification variables in the form [**P]. The behavior is consistent
with type variables’ described above as parameter specification variables are
treated by the typing module as a specialized type variable. The one exception
to this is that a list of types can be used to substitute a ParamSpec:
>>> class Z[T, **P]: ... # T is a TypeVar; P is a ParamSpec
...
>>> Z[int, [dict, float]]
__main__.Z[int, [dict, float]]
Classes generic over a ParamSpec can also be created using explicit
inheritance from Generic. In this case, ** is not used:
from typing import ParamSpec, Generic
P = ParamSpec('P')
class Z(Generic[P]):
...
Another difference between TypeVar and ParamSpec is that a
generic with only one parameter specification variable will accept
parameter lists in the forms X[[Type1, Type2, ...]] and also
X[Type1, Type2, ...] for aesthetic reasons. Internally, the latter is converted
to the former, so the following are equivalent:
>>> class X[**P]: ...
...
>>> X[int, str]
__main__.X[[int, str]]
>>> X[[int, str]]
__main__.X[[int, str]]
Note that generics with ParamSpec may not have correct
__parameters__ after substitution in some cases because they
are intended primarily for static type checking.
Changed in version 3.10: Generic can now be parameterized over parameter expressions.
See ParamSpec and PEP 612 for more details.
A user-defined generic class can have ABCs as base classes without a metaclass
conflict. Generic metaclasses are not supported. The outcome of parameterizing
generics is cached, and most types in the typing module are hashable and
comparable for equality.
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.
This means that it is possible to perform any operation or method call on a
value of type Any and assign it to any variable:
from typing import Any
a: Any = None
a = [] # OK
a = 2 # OK
s: str = ''
s = a # OK
def foo(item: Any) -> int:
# Passes type checking; 'item' could be any type,
# and that type might have a 'bar' method
item.bar()
...
Notice that no type checking is performed when assigning a value of type
Any to a more precise type. For example, the static type checker did
not report an error when assigning a to s even though s was
declared to be of type str and receives an int value at
runtime!
Furthermore, all functions without a return type or parameter types will
implicitly default to using Any:
def legacy_parser(text):
...
return data
# A static type checker will treat the above
# as having the same signature as:
def legacy_parser(text: Any) -> Any:
...
return data
This behavior allows Any to be used as an escape hatch when you
need to mix dynamically and statically typed code.
Contrast the behavior of Any with the behavior of object.
Similar to Any, every type is a subtype of object. However,
unlike Any, the reverse is not true: object is not a
subtype of every other type.
That means when the type of a value is object, a type checker will
reject almost all operations on it, and assigning it to a variable (or using
it as a return value) of a more specialized type is a type error. For example:
def hash_a(item: object) -> int:
# Fails type checking; an object does not have a 'magic' method.
item.magic()
...
def hash_b(item: Any) -> int:
# Passes type checking
item.magic()
...
# Passes type checking, since ints and strs are subclasses of object
hash_a(42)
hash_a("foo")
# Passes type checking, since Any is compatible with all types
hash_b(42)
hash_b("foo")
Use object to indicate that a value could be any type in a typesafe
manner. Use Any to indicate that a value is dynamically typed.
Nominal vs structural subtyping¶
Initially PEP 484 defined the Python static type system as using
nominal subtyping. This means that a class A is allowed where
a class B is expected if and only if A is a subclass of B.
This requirement previously also applied to abstract base classes, such as
Iterable. The problem with this approach is that a class had
to be explicitly marked to support them, which is unpythonic and unlike
what one would normally do in idiomatic dynamically typed Python code.
For example, this conforms to PEP 484:
from collections.abc import Sized, Iterable, Iterator
class Bucket(Sized, Iterable[int]):
...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[int]: ...
PEP 544 allows to solve this problem by allowing users to write
the above code without explicit base classes in the class definition,
allowing Bucket to be implicitly considered a subtype of both Sized
and Iterable[int] by static type checkers. This is known as
structural subtyping (or static duck-typing):
from collections.abc import Iterator, Iterable
class Bucket: # Note: no base classes
...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[int]: ...
def collect(items: Iterable[int]) -> int: ...
result = collect(Bucket()) # Passes type check
Moreover, by subclassing a special class Protocol, a user
can define new custom protocols to fully enjoy structural subtyping
(see examples below).
Module contents¶
The typing module defines the following classes, functions and decorators.
Special typing primitives¶
Special types¶
These can be used as types in annotations. They do not support subscription
using [].
- typing.Any¶
Special type indicating an unconstrained type.
Changed in version 3.11:
Anycan now be used as a base class. This can be useful for avoiding type checker errors with classes that can duck type anywhere or are highly dynamic.
- typing.AnyStr¶
-
Definition:
AnyStr = TypeVar('AnyStr', str, bytes)
AnyStris meant to be used for functions that may acceptstrorbytesarguments but cannot allow the two to mix.For example:
def concat(a: AnyStr, b: AnyStr) -> AnyStr: return a + b concat("foo", "bar") # OK, output has type 'str' concat(b"foo", b"bar") # OK, output has type 'bytes' concat("foo", b"bar") # Error, cannot mix str and bytes
Note that, despite its name,
AnyStrhas nothing to do with theAnytype, nor does it mean “any string”. In particular,AnyStrandstr | bytesare different from each other and have different use cases:# Invalid use of AnyStr: # The type variable is used only once in the function signature, # so cannot be "solved" by the type checker def greet_bad(cond: bool) -> AnyStr: return "hi there!" if cond else b"greetings!" # The better way of annotating this function: def greet_proper(cond: bool) -> str | bytes: return "hi there!" if cond else b"greetings!"
Deprecated since version 3.13, will be removed in version 3.18: Deprecated in favor of the new type parameter syntax. Use
class A[T: (str, bytes)]: ...instead of importingAnyStr. See PEP 695 for more details.In Python 3.16,
AnyStrwill be removed fromtyping.__all__, and deprecation warnings will be emitted at runtime when it is accessed or imported fromtyping.AnyStrwill be removed fromtypingin Python 3.18.
- typing.LiteralString¶
Special type that includes only literal strings.
Any string literal is compatible with
LiteralString, as is anotherLiteralString. However, an object typed as juststris not. A string created by composingLiteralString-typed objects is also acceptable as aLiteralString.Example:
def run_query(sql: LiteralString) -> None: ... def caller(arbitrary_string: str, literal_string: LiteralString) -> None: run_query("SELECT * FROM students") # OK run_query(literal_string) # OK run_query("SELECT * FROM " + literal_string) # OK run_query(arbitrary_string) # type checker error run_query( # type checker error f"SELECT * FROM students WHERE name = {arbitrary_string}" )
LiteralStringis useful for sensitive APIs where arbitrary user-generated strings could generate problems. For example, the two cases above that generate type checker errors could be vulnerable to an SQL injection attack.See PEP 675 for more details.
Added in version 3.11.
- typing.Never¶
- typing.NoReturn¶
NeverandNoReturnrepresent the bottom type, a type that has no members.They can be used to indicate that a function never returns, such as
sys.exit():from typing import Never # or NoReturn def stop() -> Never: raise RuntimeError('no way')
Or to define a function that should never be called, as there are no valid arguments, such as
assert_never():from typing import Never # or NoReturn def never_call_me(arg: Never) -> None: pass def int_or_str(arg: int | str) -> None: never_call_me(arg) # type checker error match arg: case int(): print("It's an int") case str(): print("It's a str") case _: never_call_me(arg) # OK, arg is of type Never (or NoReturn)
NeverandNoReturnhave the same meaning in the type system and static type checkers treat both equivalently.Added in version 3.6.2: Added
NoReturn.Added in version 3.11: Added