Type hints describe what a function expects and returns. Python ignores them at runtime; static checkers (mypy, pyright) and IDEs use them to catch bugs before the code runs.
Annotation basics
def greet(name: str, times: int = 1) -> str:
return ", ".join([f"hi {name}"] * times)
count: int = 0
scores: dict[str, list[float]] = {"asha": [9.5, 8.0]}
point: tuple[int, int] = (3, 4)
ids: tuple[int, ...] = (1, 2, 3) # variable-length tuple
print(greet("ravi", 2)) # hi ravi, hi ravi
print(greet.__annotations__) # {'name': <class 'str'>, 'times': <class 'int'>, 'return': <class 'str'>}
print(greet(42)) # hi 42 ← runs fine: hints are NOT enforced at runtimeThe vocabulary
| Hint | Meaning |
|---|---|
list[int], dict[str, int], set[str] | Built-in generics (3.9+) — no typing.List needed |
int | None / Optional[int] | May be None (the | form is 3.10+) |
int | str / Union[int, str] | One of several types |
Any | Opt out of checking for this value |
object | Any value, but you may only use what every object supports — safer than Any |
Callable[[int, str], bool] | A function taking (int, str) and returning bool |
Iterable[T], Sequence[T], Mapping[K, V] | Accept the abstract type (from collections.abc) in parameters |
Literal["r", "w"] | Only these exact values |
Final | Must not be reassigned |
ClassVar[int] | A class-level attribute, not an instance field |
Self | The type of the current class — for fluent methods (3.11+) |
Never / NoReturn | Function never returns normally (always raises) |
from collections.abc import Iterable, Callable
from typing import Literal
def total(xs: Iterable[float]) -> float: # accepts list, tuple, generator…
return sum(xs)
def apply(f: Callable[[int], int], x: int) -> int:
return f(x)
def open_mode(mode: Literal["r", "w"]) -> str:
return f"mode={mode}"
print(total([1.5, 2.5]), total(x / 2 for x in range(3))) # 4.0 1.5
print(apply(lambda n: n * n, 7), open_mode("w")) # 49 mode=wStructured data: TypedDict, NamedTuple, Protocol
from typing import TypedDict, NotRequired, NamedTuple, Protocol
class Movie(TypedDict):
title: str
year: int
rating: NotRequired[float] # key may be absent
class Point(NamedTuple):
x: float
y: float
class SupportsClose(Protocol): # structural: anything with close() matches
def close(self) -> None: ...
def shutdown(things: list[SupportsClose]) -> int:
for t in things:
t.close()
return len(things)
class Socket:
def close(self) -> None:
pass
m: Movie = {"title": "Dune", "year": 2021}
print(m["title"], Point(1, 2).x, shutdown([Socket(), Socket()])) # Dune 1 2Generics: TypeVar and the 3.12 syntax
from typing import TypeVar
T = TypeVar("T") # pre-3.12 style
def first_old(xs: list[T]) -> T:
return xs[0]
def first[T](xs: list[T]) -> T: # 3.12+ style: same meaning
return xs[0]
def largest[N: (int, float)](a: N, b: N) -> N: # constrained type parameter
return a if a > b else b
class Box[T]:
def __init__(self, item: T) -> None:
self.item = item
def get(self) -> T:
return self.item
type Matrix = list[list[float]] # type alias statement (3.12+)
print(first_old(["a"]), first([1, 2]), largest(2.5, 1.0), Box("x").get()) # a 1 2.5 x
print(Matrix.__value__) # list[list[float]]Overloads, Self, cast and TYPE_CHECKING
from typing import overload, Self, cast, TYPE_CHECKING
if TYPE_CHECKING: # only True for the type checker — avoids import cycles
from decimal import Decimal
@overload
def parse(x: str) -> int: ...
@overload
def parse(x: bytes) -> str: ...
def parse(x): # the one real implementation
return int(x) if isinstance(x, str) else x.decode()
class Query:
def __init__(self) -> None:
self.parts: list[str] = []
def where(self, cond: str) -> Self: # subclasses get their own type back
self.parts.append(cond)
return self
def to_money(x: object) -> "Decimal":
return cast("Decimal", x) # tells the checker "trust me" — no runtime effect
print(parse("42"), parse(b"hi"), Query().where("a").where("b").parts) # 42 hi ['a', 'b']Running a type checker
$ pip install mypy
$ mypy app.py # or: pyright app.py
$ mypy --strict src/ # require annotations everywhere# app.py
def average(xs: list[float]) -> float:
return sum(xs) / len(xs)
average(["1", "2"]) # a bug mypy finds without running anythingapp.py:5: error: List item 0 has incompatible type "str"; expected "float" [list-item]
app.py:5: error: List item 1 has incompatible type "str"; expected "float" [list-item]
Found 2 errors in 1 file (checked 1 source file)Annotations at runtime
import typing
def make() -> Widget: # Widget doesn't exist yet
return Widget()
class Widget:
size: int = 3
print(typing.get_type_hints(make)) # {'return': <class '__main__.Widget'>}
print(typing.get_type_hints(Widget)) # {'size': <class 'int'>}Does Python enforce type hints?
Optional[int] vs a default of None?
What is a Protocol and how does it differ from an ABC?