Type Hints
|
This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A type hint documents the expected type of a variable, parameter, or return value. Python never checks these at runtime — they exist for readers and for external tools.
Why Type Hints, and Basic Annotations
Type hints improve documentation and enable tooling (autocomplete, refactoring, static analysis) — they are not enforced by the interpreter. This distinction is explained in the
typing module documentation and formalized in
PEP 484, which introduced the syntax:
x: int = 5
name: str = "Ada"
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
# Hints are pure documentation at runtime -- this "wrong" call still executes fine:
print(greet(42)) # 'Hello, 42!' -- name violates the `str` hint, but nothing checks it
The interpreter never inspects x: int or def greet(name: str, times: int = 1) → str to reject
mismatched values — see Static Checkers: mypy and pyright below for how a separate tool catches this instead.
Generics: list[int], dict[str, int], and Unions
Since Python 3.9, built-in collections are subscriptable directly — list[int], dict[str, int],
tuple[int, str] — without importing List/Dict from typing. Full generic-alias coverage is in
Generic Alias Types:
def total(values: list[int]) -> int:
return sum(values)
def counts(words: list[str]) -> dict[str, int]:
result: dict[str, int] = {}
for word in words:
result[word] = result.get(word, 0) + 1
return result
Modern unions: X | None (3.10+)
Since Python 3.10, | builds a union type directly, per
typing.Union:
def find(items: list[str], target: str) -> int | None:
for i, item in enumerate(items):
if item == target:
return i
return None # int | None covers this
Older code: Optional[X] and Union[X, Y]
Before 3.10 (or for compatibility with older code), use Optional and Union from typing, documented
at typing.Optional:
from typing import Optional, Union
def find(items: list, target) -> Optional[int]: # same as int | None
for i, item in enumerate(items):
if item == target:
return i
return None
def parse(value: Union[str, int]) -> str: # same as str | int
return str(value)
TypeVar, Generic Functions and Classes, and Protocol
TypeVar for generic functions
A TypeVar lets a function’s parameter and return type vary together while staying consistent across
one call — documented at
typing.TypeVar:
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
first([1, 2, 3]) # T is int; returns an int
first(["a", "b"]) # T is str; returns a str
Generic classes
A class can be generic over the same TypeVar, documented under
typing.Generic:
from typing import Generic, TypeVar
T = TypeVar("T")
class Box(Generic[T]):
def __init__(self, item: T) -> None:
self.item = item
def get(self) -> T:
return self.item
int_box: Box[int] = Box(42)
str_box: Box[str] = Box("hello")
Protocol for structural typing
A Protocol describes an interface by shape — any object with matching methods satisfies it, with no
explicit inheritance required ("duck typing" made checkable). See
typing.Protocol:
from typing import Protocol
class SupportsClose(Protocol):
def close(self) -> None: ...
class FileHandle:
def close(self) -> None:
print("closed")
def shut_down(resource: SupportsClose) -> None:
resource.close()
shut_down(FileHandle()) # OK: FileHandle has a matching close() method
Callable[[int], str]
Callable[[ArgTypes], ReturnType] annotates a function value, documented at
typing.Callable:
from typing import Callable
def apply(func: Callable[[int], str], value: int) -> str:
return func(value)
apply(lambda n: f"#{n}", 7) # "#7"
Static Checkers: mypy and pyright
Type hints are read by opt-in static checkers such as mypy and pyright — they analyze source without running it and report mismatches the interpreter itself never catches. Install and run mypy from the command line:
pip install mypy
mypy my_module.py
Given the earlier greet(name: str, times: int = 1) → str example, mypy flags the misordered call at
analysis time, before the program ever runs:
$ mypy my_module.py
my_module.py:10: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)
Because hints carry no runtime effect (see Why Type Hints, and Basic Annotations above), skipping
mypy/pyright means Python happily executes the same mismatched call — the checker is the only thing
enforcing the contract the annotations describe. Background and the annotation object model itself
(annotations, string vs. evaluated forms) are covered in
the Annotations Best Practices HOWTO.
See Also
-
Dataclasses and Enums — typed field declarations on
dataclass-decorated classes. -
Functions — the parameter styles that function annotations attach to.