Python Typing
The typing and typing_extensions modules support the type annotation system in the Python language.
The type annotation system has evolved rapidly with multiple instances of breaking changes. For an overview of the entire system, see Type Annotation.
Contents
-
Python Typing
-
Primitive Types
- AbstractSet
- Any
- AnyStr
- BinaryIO
- ByteString
- Callable
- ChainMap
- Collection
- Container
- Counter
- DefaultDict
- Deque
- Dict
- FrozenSet
- ItemsView
- IO
- KeysView
- List
- Literal
- LiteralString
- Mapping
- MappingView
- Match
- MutableMapping
- MutableSequence
- MutableSet
- NamedTuple
- Optional
- OrderedDict
- Pattern
- Set
- Sequence
- TextIO
- Tuple
- TypedDict
- Union
- ValuesView
- Annotations for Type Hints
- Annotations for Annotations
- Annotations for Logic Checks
- Custom Types
- Protocols
- Generic Types
-
Primitive Types
Primitive Types
AbstractSet
Do not use typing.AbstractSet[...] in Python 3.9+; instead use collections.abc.Set[...].
Any
Any satisfies all type checking, which can be useful for making things 'just work'.
AnyStr
If a function can take either a normal string or a byte string (b""), annotate the argument with AnyStr.
This will satisfy type checkers while still catching issues relating to the combination of these incompatible types
def concat(a: AnyStr, b: AnyStr) -> AnyStr: return a + b # pass concat("foo", "bar") # pass concat(b"foo", b"bar") # fail concat("foo", b"bar")
BinaryIO
A subclass of typing.IO specific to byte streams plus type annotations for a few byte stream specific methods. Leaving the second point aside, equivalent to IO[bytes].
ByteString
Do not use typing.ByteString[...] in Python 3.9+; instead use collections.abc.ByteString[...].
Callable
Do not use typing.Callable[...] in Python 3.9+; instead use collections.abc.Callable[...].
ChainMap
Do not use typing.ChainMap[...] in Python 3.9+; instead use collections.chainmap[...].
Collection
Do not use typing.Collection[...] in Python 3.9+; instead use collections.abc.Collection[...].
Container
Do not use typing.Container[...] in Python 3.9+; instead use collections.abc.Container[...].
Counter
Do not use typing.Counter[...] in Python 3.9+; instead use collections.counter[...].
DefaultDict
Do not use typing.DefaultDict[...] in Python 3.9+; instead use collections.defaultdict[...].
Deque
Do not use typing.Deque[...] in Python 3.9+; instead use collections.deque[...].
Dict
Do not use typing.Dict[...] in Python 3.9+; instead use the built-in dict[...].
FrozenSet
Do not use typing.FrozenSet[...] in Python 3.9+; instead use the built-in frozenset[...].
ItemsView
Do not use typing.ItemsView[...] in Python 3.9+; instead use collections.abc.ItemsView[...].
IO
A byte or string stream, such as what is returned by open(). Can be type constrained to one or the other with IO[bytes] or IO[str].
KeysView
Do not use typing.KeysView[...] in Python 3.9+; instead use collections.abc.KeysView[...].
List
Do not use typing.List[...] in Python 3.9+; instead use the built-in list[...].
Literal
To annotate an argument or return value that must be one of a set of literal values, use Literal.
from typing import Literal def true() -> Literal[True]: return True def open_helper(file: str, mode: Literal['r', 'rb', 'w', 'wb']) -> None: pass
LiteralString
To annotate a string argument or return value that must one of...
- a literal string value
a string value annotated as Literal or LiteralString
- a combination of the above
...use LiteralString. As an example, consider SQL templating functions.
from typing import LiteralString def run_query(sql: LiteralString) -> None pass def caller(arbitrary_string: str, literal_string: LiteralString) -> None: # pass, because is a literal string run_query("SELECT * FROM students") # pass, because is a value annotated as a LiteralString run_query(literal_string) # pass, because is a combination of a literal string and a value annotated as a LiteralString run_query("SELECT * FROM " + literal_string) run_query(arbitrary_string) # type checker error run_query( # type checker error f"SELECT * FROM students WHERE name = {arbitrary_string}" )
This adds a degree of safety by way of the type checker.
The following str methods all preserve LiteralString annotation.
capitalize
casefold
center
expandtabs
format
join
ljust
lower
lstrip
partition
removeprefix
removesuffix
replace
rjust
rpartition
rsplit
rstrip
split
splitlines
strip
swapcase
title
upper
zfill
__add__
__iter__
__mod__
__mul__
__repr__
__rmul__
__str__
Mapping
Do not use typing.Mapping[...] in Python 3.9+; instead use collections.abc.Mapping[...].
MappingView
Do not use typing.MappingView[...] in Python 3.9+; instead use collections.abc.MappingView[...].
Match
Do not use typing.Match[...] in Python 3.9+; instead use re.Match[...].
MutableMapping
Do not use typing.MutableMapping[...] in Python 3.9+; instead use collections.abc.MutableMapping[...].
MutableSequence
Do not use typing.MutableSequence[...] in Python 3.9+; instead use collections.abc.MutableSequence[...].
MutableSet
Do not use typing.MutableSet[...] in Python 3.9+; instead use collections.abc.MutableSet[...].
NamedTuple
Named tuples are created like:
from collection import namedtuple Employee = namedtuple('Employee', ['name', 'id'])
To create a named tuple with type annotations, the equivalent code is:
from typing import NamedTuple class User(NamedTuple): name: str uid: int
It is also possible to insert methods and docstrings into named tuples created in this manner.
To create a named tuple with generic types, try:
from typing import NamedTuple, Generic, TypeVar T = TypeVar('T') class User(NamedTuple, Generic[T]): own_group: T all_groups: list[T]
Optional
To annotate an argument that can also be None, use Optional.
from typing import Optional def int_or_none(arg: Optional[int]) -> None: if arg is not None: # type checkers understand type guards; `arg` is checked as an `int` here arg += 1
OrderedDict
Do not use typing.OrderedDict[...] in Python 3.9+; instead use collections.ordereddict[...].
Pattern
Do not use typing.Pattern[...] in Python 3.9+; instead use re.Pattern[...].
Set
Do not use typing.Set[...] in Python 3.9+; instead use the built-in set[...].
Sequence
Do not use typing.Sequence[...] in Python 3.9+; instead use collections.abc.Sequence[...].
TextIO
A subclass of typing.TextIO specific to string streams plus type annotations for a few string stream specific methods. Leaving the second point aside, equivalent to IO[str].
The added method annotations include:
buffer
encoding
errors
line_buffering
newlines
Tuple
Do not use typing.Tuple[...] in Python 3.9+; instead use the built-in tuple[...].
TypedDict
To create a dictionary with type annotations, try:
from typing import TypedDict class Point(TypedDict): x: int y: int label: str # alternatively written as: #Point = TypedDict('Point', {'x': int, 'y': int, 'label': str}) # pass a: Point = {'x': 1, 'y': 2, 'label': 'good'} # fail b: Point = {'z': 3, 'label': 'bad'}
By default, all attributes in a TypedDict are required in order to pass a type checker. Annotate optional attributes with NotRequired.
from typing import TypedDict, NotRequired class Point(TypedDict): x: int y: int NotRequired[label]: str
The inverse behavior can also be acheived.
from typing import TypedDict, Required class Point(TypedDict, total=False): x: Required[int] y: Required[int] label: str # alternatively written as: #Point = TypedDict('Point', {'x': Required[int], 'y': Required[int], 'label': str}, total=False)
TypedDict definitions can inherit from other TypedDict definitions.
class 3DPoint(Point): z: int
To create a TypedDict with generic types, try:
from typing import TypedDict, Generic, TypeVar T = TypeVar('T') class User(TypedDict, Generic[T]): own_group: T all_groups: list[T]
Union
To annotate an argument that can be multiple types, use Union.
from typing import Union def int_or_str(arg: Union[int, str]) -> None: if isinstance(arg, int): # type checkers understand type guards; `arg` is checked as an `int` here arg += 1 else: # type checkers also infer that `arg` is a `str` here arg += "1"
If the argument can be either a type or None, consider Optional.
See also the union operator (|).
ValuesView
Do not use typing.ValuesView[...] in Python 3.9+; instead use collections.abc.ValuesView[...].
Annotations for Type Hints
The following annotations are mostly useful for visual hints in IDEs.
Annotated
from typing import Annotated SmallInt = Annotated[int, ValueRange(0, 9)]
Annotations for Annotations
The following annotations are mostly useful for supporting annotations.
Self
For class methods that return an instance of the class, use Self. This is superior to using forward references because subclasses will automatically have the correct annotation.
from typing import Self class Foo: def return_self(self) -> Self: return self
TypeGuard
Functions like isinstance act as type guards. Type checkers understand that subsequent conditional logic will only be called if the value is of a known, constrained type.
Custom type guards can be written and annotated with TypeGuard[T]. If the function returns True, then the value is of type T.
from typing import TypeGuard def is_str_list(val: list[object]) -> TypeGuard[list[str]]: return all(isinstance(x, str) for x in val)
Annotations for Logic Checks
The following annotations are mostly useful for causing a type checker to identify issues with program logic. They may be necessary to satisfy the type checker in edge cases.
ClassVar
If there is a class attribute that should never be set on an instance, annotate with ClassVar.
from typing import ClassVar class Quadruped: feet: ClassVar[int] = 4 cat = Quadruped("cat") # fail cat.feet = 3
Final
If there is a constant that should never be changed, annotate with Final.
from typing import Final MAX_CONN: Final[int] = 1 # fail MAX_CONN += 1
Never
If there is a function that should never be called, annotate the argument with Never.
from typing import Never def never_call_me(arg: Never) -> None: pass # fail def do_it_anyway(arg: int | str) -> None: never_call_me(arg) # pass: arg is either int or str so the default case is never reached def int_or_str(arg: int | str) -> None: match arg: case int(): pass case str(): pass case _: never_call_me(arg)
NoReturn
Do not use NoReturn in Python 3.11+; instead use Never.
Custom Types
To create a custom type, use NewType. Type checkers will treat the custom type as a subclass of the original type, while rejecting passed arguments of the original type.
from typing import NewType UserId = NewType('UserId', int) # passes user_a = get_user_name(UserId(42351)) # fails user_b = get_user_name(-1)
Given the above example, at runtime, UserId returns a callable that immediately returns the original value. This leads to three properties:
UserId(value) has minimal runtime overhead
an instance of UserId cannot be returned at runtime; UserId(value) immediately evaluates to value
UserId is not subclassable (except by chaining NewType: ProUserId = NewType('ProUserId', UserId))
Protocols
Protocol is a base class for protocols.
from typing import Protocol class SomeProtocol(Protocol): def meth(self) -> int: ... class A: def meth(self) -> int: return 0 class B: def meth(self) -> int: return 1 def func(c: SomeProtocol) -> int: return c.meth() # pass func(A()) # pass func(B())
Protocol classes can be generic as well.
from typing import TypeVar, Protocol T = TypeVar('T') class GenericProtocol(Protocol[T]): def meth(self) -> T: ...
Runtime Checks
If a protocol is decorated with @runtime_checkable, then it can be used at runtime for isinstance and issubclass checking.
from typing import Protocol, runtime_checkable @runtime_checkable class Closable(Protocol): def close(self): ... assert isinstance(open('/some/file'), Closable)
Note that method type signatures are not checked; only the presence of the required methods. This effectively implements duck type checking.
Generic Types
To annotate a generic type, use TypeVar.
from collections.abc import Sequence from typing import TypeVar T = TypeVar('T') def first(l: Sequence[T]) -> T: return l[0]
There are additional type constructs for variadic generics.
For example, to constrain the type of the first or last value in a tuple while leaving others alone, use TypeVarTuple.
from typing import TypeVar, TypeVarTuple T = TypeVar('T') Ts = TypeVarTuple('Ts') def move_first_element_to_last(tup: tuple[T, *Ts]) -> tuple[*Ts, T]: return (*tup[1:], tup[0])
TypeVarTuple is only valid when used with the expansion operator.
from typing import TypeVar, TypeVarTuple T = TypeVar('T') Ts = TypeVarTuple('Ts') # fail x: tuple[Ts] # pass x: tuple[*Ts]
In the above context, *Ts would be equivalent to using Unpack[Ts]. Unpack exists because the expansion operator formerly could not be used in this way. Do not use Unpack in Python 3.11+.