PythonMastery
reference 5 min read · lesson 16 of 16 in Reference

Python Type Hints Cheat Sheet

1 · The lesson

read

Quick 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: int

2. Built-in Generics (3.9+)

Use the lower-case built-ins, not typing.List/typing.Dict:

HintMeans
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
OldNew (3.10+)
Union[int, str]int | str
Optional[int]int | None

4. Common typing Helpers

HintMeaning
AnyDisables 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)
NeverFunction never returns (raises / exits)
NoReturnSame 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 issueCauseFix
error: Incompatible types in assignment (expression has type "None")Assigned None to intUse int | None (and check before use)
error: Item "None" of "X | None" has no attribute "foo"Forgot to narrowif x is None: return or assert x is not None
error: Need type annotation for "x"Empty container, mypy can't inferx: list[int] = []
error: Argument 1 has incompatible type "list[int]"; expected "list[float]"Lists are invariantUse Sequence[float] instead
TypeError: 'type' object is not subscriptableUsed list[int] on Python 3.8Upgrade, or from typing import List and use List[int]
NameError: name 'Tree' is not defined in annotationSelf-referenceQuote it: "Tree", or from __future__ import annotations
Hint ignored at runtimeAnnotations don't enforce typesUse mypy / pyright, or libraries like pydantic
Optional[int] is allowed None everywhereMisunderstanding of OptionalNarrow before using; Optional[X] == X | None

See Also

Practice this

on practicepython.in

Short exercises that run in your browser and tell you what your code actually did, not just whether a test passed.