Python does not check types at run time. Type hints are annotations that state the intended types, for tools such as mypy and Pyright to check (PEP 484). The interpreter stores them and otherwise ignores them. Details of the type system are in the specification for the Python type system.
Annotations
| Place | Syntax | Added |
|---|---|---|
| Function parameters and return | def f(a: int) -> str: ... | 3.0, PEP 3107 |
| Variables | x: int = 0, x: int | 3.6, PEP 526 |
| Type aliases | type X = int | 3.12, PEP 695 |
Annotations on a function, a class or a module are recorded in its
__annotations__. A variable annotation inside a function is neither
evaluated nor stored (7.2.2). An annotation with no value binds nothing, so in a class
y: int does not create an attribute. Tools such as
dataclasses read
annotations at run time.
Deferred evaluation
Since Python 3.14, annotations are not evaluated when the function or class is
created (8.11, PEP 649 and PEP 749). The compiler instead stores them in an
__annotate__ function, which runs when something asks for them, so a
forward reference needs no quotes.
import annotationlib
def f(x: Undefined) -> None:
pass
annotationlib.get_annotations(f, format=annotationlib.Format.STRING)
# {'x': 'Undefined', 'return': 'None'}
f.__annotations__ # NameError: name 'Undefined' is not defined
annotationlib.Format | Result |
|---|---|
VALUE | Evaluated values, which may raise NameError |
FORWARDREF | Values, with ForwardRef objects for undefined names |
STRING | The approximate source text |
from __future__ import annotations (PEP 563, Python 3.7) instead stores
every annotation as a string. It still works, and is planned for deprecation and
removal, but not before Python 3.13 reaches its end of life (8.11).
Annotations run in annotation scopes. Before Python 3.14 they were evaluated when the definition ran.
Writing types
| Form | Meaning | Since |
|---|---|---|
list[int], dict[str, int] | Built-in generics (PEP 585) | 3.9 |
tuple[int, str], tuple[int, ...] | A fixed tuple, and one of any length | 3.9 |
int | None | Union (PEP 604). Also works with isinstance | 3.10 |
collections.abc.Callable[[int], str] | A callable | 3.9 |
type[C] | The class C itself, or a subclass | 3.9 |
The aliases typing.List, typing.Dict and similar are
deprecated in favour of the built-in forms.
The typing module
| Name | Meaning | Since |
|---|---|---|
Any | Compatible with every type | 3.5 |
Never, NoReturn | No value; a function that does not return | 3.11, 3.6.2 |
Self | The enclosing class (PEP 673) | 3.11 |
Literal[...] | One of these literal values (PEP 586) | 3.8 |
Final, @final | No reassignment, overriding or subclassing (PEP 591) | 3.8 |
ClassVar | A class variable, not an instance variable | 3.5.3 |
Annotated[T, ...] | A type with attached metadata (PEP 593) | 3.9 |
TypedDict | A dictionary with typed keys (PEP 589) | 3.8 |
Required, NotRequired | Mark one key (PEP 655) | 3.11 |
ReadOnly | A read-only TypedDict item (PEP 705) | 3.13 |
Protocol | Structural subtyping (PEP 544) | 3.8 |
@runtime_checkable | Allows a protocol with isinstance | 3.8 |
NamedTuple | A tuple with named, typed fields | 3.6 |
NewType | A distinct type for checkers, with no run-time cost | 3.5.2 |
TypeVar, ParamSpec, TypeVarTuple | Type parameters, before the new syntax | 3.5, 3.10, 3.11 |
Concatenate, Unpack | Parameter list and unpacking helpers | 3.10, 3.11 |
TypeGuard, TypeIs | Functions that narrow a type (PEP 647, PEP 742) | 3.10, 3.13 |
TypeAlias, TypeAliasType | Explicit alias (deprecated since 3.12), and the type of a type statement | 3.10, 3.12 |
LiteralString | A string built only from literals (PEP 675) | 3.11 |
@overload | Several signatures for one function | 3.5 |
@override | Declares that a method overrides a base method (PEP 698) | 3.12 |
@dataclass_transform | Marks dataclass-like tools (PEP 681) | 3.11 |
cast, TYPE_CHECKING | A hint with no run-time effect; a constant that is true only for checkers | 3.5, 3.5.2 |
reveal_type, assert_type, assert_never | Checks that report to the type checker | 3.11 |
Python 3.15 adds typed extra items for TypedDict (PEP 728),
TypeForm (PEP 747) and disjoint bases (PEP 800).
from typing import Protocol, TypedDict, NotRequired
class Sized(Protocol):
def size(self) -> int: ...
class Movie(TypedDict):
title: str
year: NotRequired[int]
def total(items: list[Sized]) -> int:
return sum(item.size() for item in items)
Generics syntax
Python 3.12 added a syntax for type parameters (PEP 695, Language Reference 8.10): they are written in square brackets after the name.
type_params: "[" type_param ("," type_param)* "]"
type_param: typevar | typevartuple | paramspec
typevar: identifier (":" expression)? ("=" expression)?
typevartuple: "*" identifier ("=" expression)?
paramspec: "**" identifier ("=" expression)?
def first[T](items: list[T]) -> T:
return items[0]
class Box[T]:
def __init__(self, value: T) -> None:
self.value = value
type Pair[A, B] = tuple[A, B]
def apply[**P, R](fn: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> R:
return fn(*args, **kwargs)
| Form | Meaning |
|---|---|
T | A type variable |
T: int | Bound: T must be a subtype of int |
T: (int, str) | Constrained: exactly one of these types |
*Ts | A type variable tuple, for any number of types (PEP 646) |
**P | A parameter specification (PEP 612) |
T = int | A default (PEP 696, Python 3.13). Also for *Ts and **P |
- A generic class gets
typing.Generic[T]as a base automatically. It can be subscripted, asBox[int]. - Variance is inferred from the class's use of the parameter, so no
covariant=Trueis needed. - The parameters are visible only inside the definition, through an annotation scope. Bounds, constraints and defaults are lazily evaluated.
- The parameters are stored in
__type_params__. - A class written with the new syntax cannot also list
Genericas a base, which raisesTypeError. Type checkers also reject an oldTypeVarused in such a definition.
Stub files
A stub is a .pyi file holding only type information (PEP 484). Stubs
describe modules written in C, and give types to code that has none. Type checkers read
the stub instead of the module, and the interpreter never imports it.
import sys
from typing import overload
__all__ = ["read", "parse"]
MAX_SIZE: int
@overload
def read(path: str) -> str: ...
@overload
def read(path: bytes) -> bytes: ...
def parse(text: str, strict: bool = False, depth: int = ...) -> dict[str, object]: ...
class Reader:
size: int
def __init__(self, path: str) -> None: ...
if sys.version_info >= (3, 12):
def extra() -> None: ...
.pyi stub: signatures, a variable with no value, and a version check- A stub uses the syntax of a Python module. Function and class bodies are
.... - Simple default values are written out, and
...stands in for anything more complex. - Forward references need no quotes, since a stub is not run.
- Names that exist only in the stub should start with an underscore. Stubs should not
use
__future__imports. sys.version_infoandsys.platformchecks select declarations for a version or platform.- An imported name is not exported from the stub unless it is written
import x as xorfrom m import x as x, or comes fromfrom m import *. __all__should match the real module.
Distributing stubs
| Approach | File or package |
|---|---|
| Beside the module (preferred) | module.py and module.pyi |
| Inline types | A py.typed marker file in the package, which applies to its sub-packages |
| A separate stub package | A package named foopkg-stubs |
| Partial stubs | partial\n in the py.typed file |
| Typeshed | The shared stub collection, published as types- packages |
Type checkers search for a module in this order:
- Stubs or source named at the start of the search path, such as
$MYPYPATH. - The code being checked.
- Typeshed's stubs for the standard library.
foopkg-stubspackages.- Installed packages that include
py.typed. - Third-party stubs bundled with the checker.