reference
5 min read
·
lesson 16 of 16 in Reference
Python Type Hints Cheat Sheet
1 · The lesson
readQuick reference for Python type hints, generics, and typing helpers. For the full tutorial see Type Hints.
1. Basics
python
def greet(name: str, times: int = 1) -> str: return (f"hi {name} " * times).strip() count: int = 0 ratio: float = 0.5 done: bool = False items: list[str] = []
Variable annotations without a value are valid:
python
class User:
name: str
age: int2. Built-in Generics (3.9+)
Use the lower-case built-ins, not typing.List/typing.Dict:
| Hint | Means |
|---|---|
list[int] | List of ints |
dict[str, int] | Dict mapping str -> int |
set[str] | Set of strings |
frozenset[int] | Frozenset of ints |
tuple[int, str] | Fixed 2-tuple |
tuple[int, ...] | Variable-length tuple of ints |
type[User] | The User class itself (not an instance) |
3. Union & Optional (3.10+)
python
def parse(s: str) -> int | None: try: return int(s) except ValueError: return None x: int | str = "hi" # either type
| Old | New (3.10+) |
|---|---|
Union[int, str] | int | str |
Optional[int] | int | None |
4. Common typing Helpers
| Hint | Meaning |
|---|---|
Any | Disables type checking for this value |
Callable[[int, str], bool] | Function taking (int, str), returning bool |
Callable[..., T] | Any args, returns T |
Iterable[T] | Anything for-able |
Iterator[T] | Has __next__ |
Generator[Y, S, R] | Yields Y, sends S, returns R |
Sequence[T] | Indexed, len-able, immutable-ish view |
MutableSequence[T] | Like list |
Mapping[K, V] | Read-only dict-like |
MutableMapping[K, V] | Like dict |
Awaitable[T] | Anything await-able |
Type[T] | The class itself, not an instance |
Self (3.11+) | The current class (for fluent APIs / factories) |
Never | Function never returns (raises / exits) |
NoReturn | Same as Never, older name |
python
from typing import Iterable, Callable, Self def total(xs: Iterable[float]) -> float: return sum(xs) def apply(fn: Callable[[int], int], xs: list[int]) -> list[int]: return [fn(x) for x in xs] class Builder: def step(self, x: int) -> Self: ... return self
5. Generics
3.12+ has clean syntax:
python
def first[T](xs: list[T]) -> T: return xs[0] class Stack[T]: def __init__(self) -> None: self._data: list[T] = [] def push(self, x: T) -> None: self._data.append(x) def pop(self) -> T: return self._data.pop()
setup added so this can run · defines T
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) T = _AutoMock('T')
Pre-3.12 with TypeVar:
python
from typing import TypeVar, Generic T = TypeVar("T") def first(xs: list[T]) -> T: return xs[0] class Stack(Generic[T]): def __init__(self) -> None: self._data: list[T] = []
Bounds and constraints:
python
N = TypeVar("N", bound=float) # any subtype of float S = TypeVar("S", str, bytes) # exactly one of these
setup added so this can run · defines TypeVar
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def TypeVar(*_a, **_kw): print('-> TypeVar() called') return _AutoMock('TypeVar()')
6. Protocol (Structural Typing)
Duck typing with type checking:
python
from typing import Protocol class HasName(Protocol): name: str def greet(o: HasName) -> str: return f"hi {o.name}" class User: name = "Ada" greet(User()) # passes — User structurally matches HasName
7. TypedDict
Type a dict by its keys:
python
from typing import TypedDict, NotRequired class UserDict(TypedDict): id: int name: str email: NotRequired[str] # optional key u: UserDict = {"id": 1, "name": "Ada"}
8. Literal, NewType, Final, ClassVar
python
from typing import Literal, NewType, Final, ClassVar def colour(c: Literal["red", "green", "blue"]) -> str: return c.upper() UserId = NewType("UserId", int) def fetch(uid: UserId) -> dict: ... fetch(UserId(42)) # NOT fetch(42) — mypy flags it MAX: Final = 100 # cannot be reassigned (by type checker) class Cfg: DEFAULTS: ClassVar[dict[str, int]] = {} # shared class attr, not per instance
9. Forward References
When the type isn't defined yet (e.g. self-referential class):
python
class Tree: def __init__(self, value: int, left: "Tree | None" = None, right: "Tree | None" = None): ...
Or stringify everything by default:
python
from __future__ import annotations class Tree: def __init__(self, value: int, left: Tree | None = None): ...
Common Patterns
python
# Typed function def divide(a: float, b: float) -> float: if b == 0: raise ValueError("zero divisor") return a / b # Typed dataclass from dataclasses import dataclass @dataclass class Point: x: float y: float = 0.0 tags: list[str] = field(default_factory=list) # Generic container (3.12+) class Cache[K, V]: def __init__(self) -> None: self._d: dict[K, V] = {} def get(self, k: K) -> V | None: return self._d.get(k) def set(self, k: K, v: V) -> None: self._d[k] = v # Protocol for duck typing from typing import Protocol class Closeable(Protocol): def close(self) -> None: ... def shutdown(things: list[Closeable]) -> None: for t in things: t.close() # Overload (multiple signatures) from typing import overload @overload def parse(s: str) -> int: ... @overload def parse(s: bytes) -> bytes: ... def parse(s): return int(s) if isinstance(s, str) else s # Self for fluent / factory from typing import Self class QueryBuilder: def where(self, cond: str) -> Self: ...; return self @classmethod def of(cls, table: str) -> Self: return cls()
setup added so this can run · defines field, V, K
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def field(*_a, **_kw): print('-> field() called') return _AutoMock('field()') V = _AutoMock('V') K = _AutoMock('K')
Common Errors
| mypy / runtime issue | Cause | Fix |
|---|---|---|
error: Incompatible types in assignment (expression has type "None") | Assigned None to int | Use int | None (and check before use) |
error: Item "None" of "X | None" has no attribute "foo" | Forgot to narrow | if x is None: return or assert x is not None |
error: Need type annotation for "x" | Empty container, mypy can't infer | x: list[int] = [] |
error: Argument 1 has incompatible type "list[int]"; expected "list[float]" | Lists are invariant | Use Sequence[float] instead |
TypeError: 'type' object is not subscriptable | Used list[int] on Python 3.8 | Upgrade, or from typing import List and use List[int] |
NameError: name 'Tree' is not defined in annotation | Self-reference | Quote it: "Tree", or from __future__ import annotations |
| Hint ignored at runtime | Annotations don't enforce types | Use mypy / pyright, or libraries like pydantic |
Optional[int] is allowed None everywhere | Misunderstanding of Optional | Narrow before using; Optional[X] == X | None |
See Also
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code actually did, not just whether a test passed.