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
Primitive Types
Any
Any satisfies all type checking, which can be useful for making things 'just work'.
Callable
Do not use typing.Callable[...] in Python 3.9+; instead use collections.abc.Callable[...].
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__
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
Tuple
Do not use typing.Tuple[...] in Python 3.9+; instead use the built-in tuple[...].
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 (|).
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 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.
Functions
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.
Constants
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
Classes
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
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
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))
Generics
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]