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:
Any
can 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)
AnyStr
is meant to be used for functions that may acceptstr
orbytes
arguments 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,
AnyStr
has nothing to do with theAny
type, nor does it mean “any string”. In particular,AnyStr
andstr | bytes
are 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,
AnyStr
will be removed fromtyping.__all__
, and deprecation warnings will be emitted at runtime when it is accessed or imported fromtyping
.AnyStr
will be removed fromtyping
in 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 juststr
is 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}" )
LiteralString
is 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¶
Never
andNoReturn
represent 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)
Never
andNoReturn
have 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
Never
.
- typing.Self¶
Special type to represent the current enclosed class.
For example:
from typing import Self, reveal_type class Foo: def return_self(self) -> Self: ... return self class SubclassOfFoo(Foo): pass reveal_type(Foo().return_self()) # Revealed type is "Foo" reveal_type(SubclassOfFoo().return_self()) # Revealed type is "SubclassOfFoo"
This annotation is semantically equivalent to the following, albeit in a more succinct fashion:
from typing import TypeVar Self = TypeVar("Self", bound="Foo") class Foo: def return_self(self: Self) -> Self: ... return self
In general, if something returns
self
, as in the above examples, you should useSelf
as the return annotation. IfFoo.return_self
was annotated as returning"Foo"
, then the type checker would infer the object returned fromSubclassOfFoo.return_self
as being of typeFoo
rather thanSubclassOfFoo
.Other common use cases include:
classmethod
s that are used as alternative constructors and return instances of thecls
parameter.Annotating an
__enter__()
method which returns self.
You should not use
Self
as the return annotation if the method is not guaranteed to return an instance of a subclass when the class is subclassed:class Eggs: # Self would be an incorrect return annotation here, # as the object returned is always an instance of Eggs, # even in subclasses def returns_eggs(self) -> "Eggs": return Eggs()
See PEP 673 for more details.
Added in version 3.11.
- typing.TypeAlias¶
Special annotation for explicitly declaring a type alias.
For example:
from typing import TypeAlias Factors: TypeAlias = list[int]
TypeAlias
is particularly useful on older Python versions for annotating aliases that make use of forward references, as it can be hard for type checkers to distinguish these from normal variable assignments:from typing import Generic, TypeAlias, TypeVar T = TypeVar("T") # "Box" does not exist yet, # so we have to use quotes for the forward reference on Python <3.12. # Using ``TypeAlias`` tells the type checker that this is a type alias declaration, # not a variable assignment to a string. BoxOfStrings: TypeAlias = "Box[str]" class Box(Generic[T]): @classmethod def make_box_of_strings(cls) -> BoxOfStrings: ...
See PEP 613 for more details.
Added in version 3.10.
Deprecated since version 3.12:
TypeAlias
is deprecated in favor of thetype
statement, which creates instances ofTypeAliasType
and which natively supports forward references. Note that whileTypeAlias
andTypeAliasType
serve similar purposes and have similar names, they are distinct and the latter is not the type of the former. Removal ofTypeAlias
is not currently planned, but users are encouraged to migrate totype
statements.
Special forms¶
These can be used as types in annotations. They all support subscription using
[]
, but each has a unique syntax.
- typing.Union¶
Union type;
Union[X, Y]
is equivalent toX | Y
and means either X or Y.To define a union, use e.g.
Union[int, str]
or the shorthandint | str
. Using that shorthand is recommended. Details:The arguments must be types and there must be at least one.
Unions of unions are flattened, e.g.:
Union[Union[int, str], float] == Union[int, str, float]
However, this does not apply to unions referenced through a type alias, to avoid forcing evaluation of the underlying
TypeAliasType
:type A = Union[int, str] Union[A, float] != Union[int, str, float]
Unions of a single argument vanish, e.g.:
Union[int] == int # The constructor actually returns int
Redundant arguments are skipped, e.g.:
Union[int, str, int] == Union[int, str] == int | str
When comparing unions, the argument order is ignored, e.g.:
Union[int, str] == Union[str, int]
You cannot subclass or instantiate a
Union
.You cannot write
Union[X][Y]
.
Changed in version 3.7: Don’t remove explicit subclasses from unions at runtime.
Changed in version 3.10: Unions can now be written as
X | Y
. See union type expressions.
- typing.Optional¶
Optional[X]
is equivalent toX | None
(orUnion[X, None]
).Note that this is not the same concept as an optional argument, which is one that has a default. An optional argument with a default does not require the
Optional
qualifier on its type annotation just because it is optional. For example:def foo(arg: int = 0) -> None: ...
On the other hand, if an explicit value of
None
is allowed, the use ofOptional
is appropriate, whether the argument is optional or not. For example:def foo(arg: Optional[int] = None) -> None: ...
Changed in version 3.10: Optional can now be written as
X | None
. See union type expressions.
- typing.Concatenate¶
Special form for annotating higher-order functions.
Concatenate
can be used in conjunction with Callable andParamSpec
to annotate a higher-order callable which adds, removes, or transforms parameters of another callable. Usage is in the formConcatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable]
.Concatenate
is currently only valid when used as the first argument to a Callable. The last parameter toConcatenate
must be aParamSpec
or ellipsis (...
).For example, to annotate a decorator
with_lock
which provides athreading.Lock
to the decorated function,Concatenate
can be used to indicate thatwith_lock
expects a callable which takes in aLock
as the first argument, and returns a callable with a different type signature. In this case, theParamSpec
indicates that the returned callable’s parameter types are dependent on the parameter types of the callable being passed in:from collections.abc import Callable from threading import Lock from typing import Concatenate # Use this lock to ensure that only one thread is executing a function # at any time. my_lock = Lock() def with_lock[**P, R](f: Callable[Concatenate[Lock, P], R]) -> Callable[P, R]: '''A type-safe decorator which provides a lock.''' def inner(*args: P.args, **kwargs: P.kwargs) -> R: # Provide the lock as the first argument. return f(my_lock, *args, **kwargs) return inner @with_lock def sum_threadsafe(lock: Lock, numbers: list[float]) -> float: '''Add a list of numbers together in a thread-safe manner.''' with lock: return sum(numbers) # We don't need to pass in the lock ourselves thanks to the decorator. sum_threadsafe([1.1, 2.2, 3.3])
Added in version 3.10.
See also
PEP 612 – Parameter Specification Variables (the PEP which introduced
ParamSpec
andConcatenate
)
- typing.Literal¶
Special typing form to define “literal types”.
Literal
can be used to indicate to type checkers that the annotated object has a value equivalent to one of the provided literals.For example:
def validate_simple(data: Any) -> Literal[True]: # always returns True ... type Mode = Literal['r', 'rb', 'w', 'wb'] def open_helper(file: str, mode: Mode) -> str: ... open_helper('/some/path', 'r') # Passes type check open_helper('/other/path', 'typo') # Error in type checker
Literal[...]
cannot be subclassed. At runtime, an arbitrary value is allowed as type argument toLiteral[...]
, but type checkers may impose restrictions. See PEP 586 for more details about literal types.Additional details:
The arguments must be literal values and there must be at least one.
Nested
Literal
types are flattened, e.g.:assert Literal[Literal[1, 2], 3] == Literal[1, 2, 3]
However, this does not apply to
Literal
types referenced through a type alias, to avoid forcing evaluation of the underlyingTypeAliasType
:type A = Literal[1, 2] assert Literal[A, 3] != Literal[1, 2, 3]
Redundant arguments are skipped, e.g.:
assert Literal[1, 2, 1] == Literal[1, 2]
When comparing literals, the argument order is ignored, e.g.:
assert Literal[1, 2] == Literal[2, 1]
You cannot subclass or instantiate a
Literal
.You cannot write
Literal[X][Y]
.
Added in version 3.8.
- typing.ClassVar¶
Special type construct to mark class variables.
As introduced in PEP 526, a variable annotation wrapped in ClassVar indicates that a given attribute is intended to be used as a class variable and should not be set on instances of that class. Usage:
class Starship: stats: ClassVar[dict[str, int]] = {} # class variable damage: int = 10 # instance variable
ClassVar
accepts only types and cannot be further subscribed.ClassVar
is not a class itself, and should not be used withisinstance()
orissubclass()
.ClassVar
does not change Python runtime behavior, but it can be used by third-party type checkers. For example, a type checker might flag the following code as an error:enterprise_d = Starship(3000) enterprise_d.stats = {} # Error, setting class variable on instance Starship.stats = {} # This is OK
Added in version 3.5.3.
- typing.Final¶
Special typing construct to indicate final names to type checkers.
Final names cannot be reassigned in any scope. Final names declared in class scopes cannot be overridden in subclasses.
For example:
MAX_SIZE: Final = 9000 MAX_SIZE += 1 # Error reported by type checker class Connection: TIMEOUT: Final[int] = 10 class FastConnector(Connection): TIMEOUT = 1 # Error reported by type checker
There is no runtime checking of these properties. See PEP 591 for more details.
Added in version 3.8.
- typing.Required¶
Special typing construct to mark a
TypedDict
key as required.This is mainly useful for
total=False
TypedDicts. SeeTypedDict
and PEP 655 for more details.Added in version 3.11.
- typing.NotRequired¶
Special typing construct to mark a
TypedDict
key as potentially missing.See
TypedDict
and PEP 655 for more details.Added in version 3.11.
- typing.ReadOnly¶
A special typing construct to mark an item of a
TypedDict
as read-only.For example:
class Movie(TypedDict): title: ReadOnly[str] year: int def mutate_movie(m: Movie) -> None: m["year"] = 1999 # allowed m["title"] = "The Matrix" # typechecker error
There is no runtime checking for this property.
See
TypedDict
and PEP 705 for more details.Added in version 3.13.
- typing.Annotated¶
Special typing form to add context-specific metadata to an annotation.
Add metadata
x
to a given typeT
by using the annotationAnnotated[T, x]
. Metadata added usingAnnotated
can be used by static analysis tools or at runtime. At runtime, the metadata is stored in a__metadata__
attribute.If a library or tool encounters an annotation
Annotated[T, x]
and has no special logic for the metadata, it should ignore the metadata and simply treat the annotation asT
. As such,Annotated
can be useful for code that wants to use annotations for purposes outside Python’s static typing system.Using
Annotated[T, x]
as an annotation still allows for static typechecking ofT
, as type checkers will simply ignore the metadatax
. In this way,Annotated
differs from the@no_type_check
decorator, which can also be used for adding annotations outside the scope of the typing system, but completely disables typechecking for a function or class.The responsibility of how to interpret the metadata lies with the tool or library encountering an
Annotated
annotation. A tool or library encountering anAnnotated
type can scan through the metadata elements to determine if they are of interest (e.g., usingisinstance()
).- Annotated[<type>, <metadata>]
Here is an example of how you might use
Annotated
to add metadata to type annotations if you were doing range analysis:@dataclass class ValueRange: lo: int hi: int T1 = Annotated[int, ValueRange(-10, 5)] T2 = Annotated[T1, ValueRange(-20, 3)]
The first argument to
Annotated
must be a valid type. Multiple metadata elements can be supplied asAnnotated
supports variadic arguments. The order of the metadata elements is preserved and matters for equality checks:@dataclass class ctype: kind: str a1 = Annotated[int, ValueRange(3, 10), ctype("char")] a2 = Annotated[int, ctype("char"), ValueRange(3, 10)] assert a1 != a2 # Order matters
It is up to the tool consuming the annotations to decide whether the client is allowed to add multiple metadata elements to one annotation and how to merge those annotations.
Nested
Annotated
types are flattened. The order of the metadata elements starts with the innermost annotation:assert Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[ int, ValueRange(3, 10), ctype("char") ]
However, this does not apply to
Annotated
types referenced through a type alias, to avoid forcing evaluation of the underlyingTypeAliasType
:type From3To10[T] = Annotated[T, ValueRange(3, 10)] assert Annotated[From3To10[int], ctype("char")] != Annotated[ int, ValueRange(3, 10), ctype("char") ]
Duplicated metadata elements are not removed:
assert Annotated[int, ValueRange(3, 10)] != Annotated[ int, ValueRange(3, 10), ValueRange(3, 10) ]
Annotated
can be used with nested and generic aliases:@dataclass class MaxLen: value: int type Vec[T] = Annotated[list[tuple[T, T]], MaxLen(10)] # When used in a type annotation, a type checker will treat "V" the same as # ``Annotated[list[tuple[int, int]], MaxLen(10)]``: type V = Vec[int]
Annotated
cannot be used with an unpackedTypeVarTuple
:type Variadic[*Ts] = Annotated[*Ts, Ann1] = Annotated[T1, T2, T3, ..., Ann1] # NOT valid
where
T1
,T2
, … areTypeVars
. This is invalid as only one type should be passed to Annotated.By default,
get_type_hints()
strips the metadata from annotations. Passinclude_extras=True
to have the metadata preserved:>>> from typing import Annotated, get_type_hints >>> def func(x: Annotated[int, "metadata"]) -> None: pass ... >>> get_type_hints(func) {'x': <class 'int'>, 'return': <class 'NoneType'>} >>> get_type_hints(func, include_extras=True) {'x': typing.Annotated[int, 'metadata'], 'return': <class 'NoneType'>}
At runtime, the metadata associated with an
Annotated
type can be retrieved via the__metadata__
attribute:>>> from typing import Annotated >>> X = Annotated[int, "very", "important", "metadata"] >>> X typing.Annotated[int, 'very', 'important', 'metadata'] >>> X.__metadata__ ('very', 'important', 'metadata')
If you want to retrieve the original type wrapped by
Annotated
, use the__origin__
attribute:>>> from typing import Annotated, get_origin >>> Password = Annotated[str, "secret"] >>> Password.__origin__ <class 'str'>
Note that using
get_origin()
will returnAnnotated
itself:>>> get_origin(Password) typing.Annotated
See also
- PEP 593 - Flexible function and variable annotations
The PEP introducing
Annotated
to the standard library.
Added in version 3.9.
- typing.TypeIs¶
Special typing construct for marking user-defined type predicate functions.
TypeIs
can be used to annotate the return type of a user-defined type predicate function.TypeIs
only accepts a single type argument. At runtime, functions marked this way should return a boolean and take at least one positional argument.TypeIs
aims to benefit type narrowing – a technique used by static type checkers to determine a more precise type of an expression within a program’s code flow. Usually type narrowing is done by analyzing conditional code flow and applying the narrowing to a block of code. The conditional expression here is sometimes referred to as a “type predicate”:def is_str(val: str | float): # "isinstance" type predicate if isinstance(val, str): # Type of ``val`` is narrowed to ``str`` ... else: # Else, type of ``val`` is narrowed to ``float``. ...
Sometimes it would be convenient to use a user-defined boolean function as a type predicate. Such a function should use
TypeIs[...]
orTypeGuard
as its return type to alert static type checkers to this intention.TypeIs
usually has more intuitive behavior thanTypeGuard
, but it cannot be used when the input and output types are incompatible (e.g.,list[object]
tolist[int]
) or when the function does not returnTrue
for all instances of the narrowed type.Using
-> TypeIs[NarrowedType]
tells the static type checker that for a given function:The return value is a boolean.
If the return value is
True
, the type of its argument is the intersection of the argument’s original type andNarrowedType
.If the return value is
False
, the type of its argument is narrowed to excludeNarrowedType
.
For example:
from typing import assert_type, final, TypeIs class Parent: pass class Child(Parent): pass @final class Unrelated: pass def is_parent(val: object) -> TypeIs[Parent]: return isinstance(val, Parent) def run(arg: Child | Unrelated): if is_parent(arg): # Type of ``arg`` is narrowed to the intersection # of ``Parent`` and ``Child``, which is equivalent to # ``Child``. assert_type(arg, Child) else: # Type of ``arg`` is narrowed to exclude ``Parent``, # so only ``Unrelated`` is left. assert_type(arg, Unrelated)
The type inside
TypeIs
must be consistent with the type of the function’s argument; if it is not, static type checkers will raise an error. An incorrectly writtenTypeIs
function can lead to unsound behavior in the type system; it is the user’s responsibility to write such functions in a type-safe manner.If a
TypeIs
function is a class or instance method, then the type inTypeIs
maps to the type of the second parameter (aftercls
orself
).In short, the form
def foo(arg: TypeA) -> TypeIs[TypeB]: ...
, means that iffoo(arg)
returnsTrue
, thenarg
is an instance ofTypeB
, and if it returnsFalse
, it is not an instance ofTypeB
.TypeIs
also works with type variables. For more information, see PEP 742 (Narrowing types withTypeIs
).Added in version 3.13.
- typing.TypeGuard¶
Special typing construct for marking user-defined type predicate functions.
Type predicate functions are user-defined functions that return whether their argument is an instance of a particular type.
TypeGuard
works similarly toTypeIs
, but has subtly different effects on type checking behavior (see below).Using
-> TypeGuard
tells the static type checker that for a given function:The return value is a boolean.
If the return value is
True
, the type of its argument is the type insideTypeGuard
.
TypeGuard
also works with type variables. See PEP 647 for more details.For example:
def is_str_list(val: list[object]) -> TypeGuard[list[str]]: '''Determines whether all objects in the list are strings''' return all(isinstance(x, str) for x in val) def func1(val: list[object]): if is_str_list(val): # Type of ``val`` is narrowed to ``list[str]``. print(" ".join(val)) else: # Type of ``val`` remains as ``list[object]``. print("Not a list of strings!")
TypeIs
andTypeGuard
differ in the following ways:TypeIs
requires the narrowed type to be a subtype of the input type, whileTypeGuard
does not. The main reason is to allow for things like narrowinglist[object]
tolist[str]
even though the latter is not a subtype of the former, sincelist
is invariant.When a
TypeGuard
function returnsTrue
, type checkers narrow the type of the variable to exactly theTypeGuard
type. When aTypeIs
function returnsTrue
, type checkers can infer a more precise type combining the previously known type of the variable with theTypeIs
type. (Technically, this is known as an intersection type.)When a
TypeGuard
function returnsFalse
, type checkers cannot narrow the type of the variable at all. When aTypeIs
function returnsFalse
, type checkers can narrow the type of the variable to exclude theTypeIs
type.
Added in version 3.10.
- typing.Unpack¶
Typing operator to conceptually mark an object as having been unpacked.
For example, using the unpack operator
*
on a type variable tuple is equivalent to usingUnpack
to mark the type variable tuple as having been unpacked:Ts = TypeVarTuple('Ts') tup: tuple[*Ts] # Effectively does: tup: tuple[Unpack[Ts]]
In fact,
Unpack
can be used interchangeably with*
in the context oftyping.TypeVarTuple
andbuiltins.tuple
types. You might seeUnpack
being used explicitly in older versions of Python, where*
couldn’t be used in certain places:# In older versions of Python, TypeVarTuple and Unpack # are located in the `typing_extensions` backports package. from typing_extensions import TypeVarTuple, Unpack Ts = TypeVarTuple('Ts') tup: tuple[*Ts] # Syntax error on Python <= 3.10! tup: tuple[Unpack[Ts]] # Semantically equivalent, and backwards-compatible
Unpack
can also be used along withtyping.TypedDict
for typing**kwargs
in a function signature:from typing import TypedDict, Unpack class Movie(TypedDict): name: str year: int # This function expects two keyword arguments - `name` of type `str` # and `year` of type `int`. def foo(**kwargs: Unpack[Movie]): ...
See PEP 692 for more details on using
Unpack
for**kwargs
typing.Added in version 3.11.
Building generic types and type aliases¶
The following classes should not be used directly as annotations. Their intended purpose is to be building blocks for creating generic types and type aliases.
These objects can be created through special syntax
(type parameter lists and the type
statement).
For compatibility with Python 3.11 and earlier, they can also be created
without the dedicated syntax, as documented below.
- class typing.Generic¶
Abstract base class for generic types.
A generic type is typically declared by adding a list of type parameters after the class name:
class Mapping[KT, VT]: def __getitem__(self, key: KT) -> VT: ... # Etc.
Such a class implicitly inherits from
Generic
. The runtime semantics of this syntax are discussed in the Language Reference.This class can then be used as follows:
def lookup_name[X, Y](mapping: Mapping[X, Y], key: X, default: Y) -> Y: try: return mapping[key] except KeyError: return default
Here the brackets after the function name indicate a generic function.
For backwards compatibility, generic classes can also be declared by explicitly inheriting from
Generic
. In this case, the type parameters must be declared separately:KT = TypeVar('KT') VT = TypeVar('VT') class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.
- class typing.TypeVar(name, *constraints, bound=None, covariant=False, contravariant=False, infer_variance=False, default=typing.NoDefault)¶
Type variable.
The preferred way to construct a type variable is via the dedicated syntax for generic functions, generic classes, and generic type aliases:
class Sequence[T]: # T is a TypeVar ...
This syntax can also be used to create bounded and constrained type variables:
class StrSequence[S: str]: # S is a TypeVar with a `str` upper bound; ... # we can say that S is "bounded by `str`" class StrOrBytesSequence[A: (str, bytes)]: # A is a TypeVar constrained to str or bytes ...
However, if desired, reusable type variables can also be constructed manually, like so:
T = TypeVar('T') # Can be anything S = TypeVar('S', bound=str) # Can be any subtype of str A = TypeVar('A', str, bytes) # Must be exactly str or bytes
Type variables exist primarily for the benefit of static type checkers. They serve as the parameters for generic types as well as for generic function and type alias definitions. See
Generic
for more information on generic types. Generic functions work as follows:def repeat[T](x: T, n: int) -> Sequence[T]: """Return a list containing n references to x.""" return [x]*n def print_capitalized[S: str](x: S) -> S: """Print x capitalized, and return x.""" print(x.capitalize()) return x def concatenate[A: (str, bytes)](x: A, y: A) -> A: """Add two strings or bytes objects together.""" return x + y
Note that type variables can be bounded, constrained, or neither, but cannot be both bounded and constrained.
The variance of type variables is inferred by type checkers when they are created through the type parameter syntax or when
infer_variance=True
is passed. Manually created type variables may be explicitly marked covariant or contravariant by passingcovariant=True
orcontravariant=True
. By default, manually created type variables are invariant. See PEP 484 and PEP 695 for more details.Bounded type variables and constrained type variables have different semantics in several important ways. Using a bounded type variable means that the
TypeVar
will be solved using the most specific type possible:x = print_capitalized('a string') reveal_type(x) # revealed type is str class StringSubclass(str): pass y = print_capitalized(StringSubclass('another string')) reveal_type(y) # revealed type is StringSubclass z = print_capitalized(45) # error: int is not a subtype of str
The upper bound of a type variable can be a concrete type, abstract type (ABC or Protocol), or even a union of types:
# Can be anything with an __abs__ method def print_abs[T: SupportsAbs](arg: T) -> None: print("Absolute value:", abs(arg)) U = TypeVar('U', bound=str|bytes) # Can be any subtype of the union str|bytes V = TypeVar('V', bound=SupportsAbs) # Can be anything with an __abs__ method
Using a constrained type variable, however, means that the
TypeVar
can only ever be solved as being exactly one of the constraints given:a = concatenate('one', 'two') reveal_type(a) # revealed type is str b = concatenate(StringSubclass('one'), StringSubclass('two')) reveal_type(b) # revealed type is str, despite StringSubclass being passed in c = concatenate('one', b'two') # error: type variable 'A' can be either str or bytes in a function call, but not both
At runtime,
isinstance(x, T)
will raiseTypeError
.- __name__¶
The name of the type variable.
- __covariant__¶
Whether the type var has been explicitly marked as covariant.
- __contravariant__¶
Whether the type var has been explicitly marked as contravariant.
- __infer_variance__¶
Whether the type variable’s variance should be inferred by type checkers.
Added in version 3.12.
- __bound__¶
The upper bound of the type variable, if any.
Changed in version 3.12: For type variables created through type parameter syntax, the bound is evaluated only when the attribute is accessed, not when the type variable is created (see Lazy evaluation).
- __constraints__¶
A tuple containing the constraints of the type variable, if any.
Changed in version 3.12: For type variables created through type parameter syntax, the constraints are evaluated only when the attribute is accessed, not when the type variable is created (see