Python Type Hints Cheat Sheet
1 · The lesson
readA type hint is a label that says what type a variable, argument or return value should have, such as name: str. Hints are for readers and for tools: Python stores them but never checks them. A type checker such as mypy or pyright reads your code and reports places where the types do not fit. This page shows the hints you will write most often. For the full tutorial, see Type Hints.
1. Basics
Put the type after a colon. The return type of a function goes after ->.
count: int = 0 def greet(name: str, n: int = 1) -> str: return name * n def log(msg: str) -> None: # -> None: the function returns nothing print(msg) names: list[str] = [] # label an empty list: the checker cannot guess the item type class User: name: str # names the attributes and their types, age: int # but sets no values print(greet("Ada ", 2)) # Ada Ada
2. Built-in generics
Since Python 3.9 you write the item types inside square brackets on the normal built-in types.
| Hint | Means |
|---|---|
list[int] | A list of ints |
dict[str, int] | A dict with str keys and int values |
set[str] | A set of strings (also frozenset[str]) |
tuple[int, str] | Exactly two items: an int, then a str |
tuple[int, ...] | A tuple of any length. Every item is an int |
type[User] | The class User itself, not an object made from it |
3. None and either-or
| means "either type" (Python 3.10+). str | None means the value may be None, so check for None before you use it.
def find(user_id: int) -> str | None: return {1: "Ada"}.get(user_id) name = find(2) if name is not None: print(name.upper()) else: print("not found") # not found x: int | str = 5 # x may be an int or a str
In older code you will see Optional[int], which means int | None, and Union[int, str], which means int | str. Both come from the typing module.
4. Functions as values
Callable describes a function. Inside the square brackets, write the argument types as a list, then the return type.
from collections.abc import Callable def run(f: Callable[[int], int]) -> int: return f(1) print(run(lambda n: n + 41)) # 42
| Hint | Means |
|---|---|
Callable[[int, str], bool] | A function that takes an int and a str, and returns a bool |
Callable[..., None] | A function with any arguments that returns nothing |
5. General types for arguments
For arguments, use the most general type that works, so callers can pass a list, a tuple or a generator. For return values, be exact. These general types come from collections.abc:
from collections.abc import Iterable, Iterator, Mapping, Sequence def total(xs: Iterable[float]) -> float: # anything a for loop can go through return sum(xs) def first(xs: Sequence[str]) -> str: # items have positions: list, tuple, str return xs[0] def show(d: Mapping[str, int]) -> None: # a dict, or similar, that you only read print(d) def nums() -> Iterator[int]: # a generator function that yields ints yield 1 print(total((19.5, 0.5))) # 20.0 print(first(("Grace", "Linus"))) # Grace
6. Records
A record is a group of named fields. Hints describe each field.
from typing import NamedTuple, NotRequired, TypedDict from dataclasses import dataclass class Movie(TypedDict): title: str year: int rating: NotRequired[float] # this key may be missing m: Movie = {"title": "Up", "year": 2009} print(type(m).__name__) # dict @dataclass class Item: name: str qty: int = 0 print(Item("pen")) # Item(name='pen', qty=0) class Point(NamedTuple): x: float y: float print(Point(1.5, 2.0).y) # 2.0
A TypedDict is a dict with fixed keys and value types. While the program runs, it is still a plain dict. In a @dataclass, the hints become the arguments of __init__. A NamedTuple is a tuple whose fields have names and types.
7. Narrow choices
from typing import ClassVar, Final, Literal, NewType def act(mode: Literal["r", "w"]) -> None: ... # mode must be exactly "r" or "w" MAX: Final = 100 # the checker reports any new value for MAX class Cfg: count: ClassVar[int] = 0 # one value shared by the class, not one per object UserId = NewType("UserId", int) uid = UserId(42) # an int that the checker keeps apart from other ints print(uid + 1) # 43
With NewType, the checker reports an error if you pass a plain 42 to a function that takes a UserId. While the program runs, uid is just an int.
8. Generics
A generic function or class works with many types. A type variable such as T links the types together. In first[T], T stands for the type of the list items, so the checker knows that first(["Ada"]) returns a str.
def first[T](xs: list[T]) -> T: # Python 3.12+ return xs[0] class Box[T]: # a generic class: a Box can hold any type T (3.12+) def __init__(self, item: T) -> None: self.item = item def biggest[N: float](xs: list[N]) -> N: # a bound: N must be float or a type the checker accepts as float, such as int return max(xs) type Pair = tuple[int, int] # a type alias: a short name for a type (3.12+) print(first(["Ada", "Hedy"])) # Ada print(Box(3).item) # 3 print(biggest([3, 7, 5])) # 7
setup added so this can run · defines T, N
# 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') N = _AutoMock('N')
Before Python 3.12, you created T with TypeVar:
from typing import Generic, TypeVar T = TypeVar("T") def last(xs: list[T]) -> T: return xs[-1] class Stack(Generic[T]): def __init__(self) -> None: self.items: list[T] = [] print(last([1, 2, 3])) # 3
9. Duck typing with Protocol
A Protocol describes what an object can do, not what class it is. Any object that has the right methods fits the protocol. The object's class does not have to inherit from the protocol. This idea is called duck typing.
from typing import Protocol class Closer(Protocol): def close(self) -> None: ... class Door: def close(self) -> None: print("door closed") def shut(x: Closer) -> None: x.close() shut(Door()) # door closed
10. Methods and overloads
from typing import NoReturn, Self, overload class Query: def where(self, s: str) -> Self: # on a subclass, where() returns the subclass type return self class Tree: def grow(self) -> "Tree": # quotes: Tree is not fully defined yet return Tree() @overload def dbl(x: int) -> int: ... @overload def dbl(x: str) -> str: ... def dbl(x): # the real function, with no hints return x * 2 def fail(msg: str) -> NoReturn: # never returns: it always raises an error raise ValueError(msg) print(dbl(4), dbl("ab")) # 8 abab
@overload tells the checker that the return type depends on the input type: an int in gives an int out, a str in gives a str out.
11. Any compared with object
from typing import Any def f(x: Any) -> None: ... # the checker stops checking x def g(x: object) -> None: ... # accepts any value, but allows almost no operations on x
With Any, the checker allows every operation on x. With object, it allows only what every object has, so you must check the type first (for example with isinstance).
12. At run time
Python does not check hints while the program runs:
from typing import get_type_hints def double(x: int) -> int: return x * 2 print(double("ab")) # abab print(get_type_hints(double)["x"]) # <class 'int'>
double("ab") runs without an error, even though the hint says int. get_type_hints() reads the hints of a function while the program runs. To see what type a checker thinks x has, write reveal_type(x) and run mypy or pyright. The checker prints the type it worked out. Libraries such as pydantic do check types while the program runs.
Common mistakes
A None default with a non-None type. def f(x: int = None) is wrong, because None is not an int. Write def f(x: int | None = None).
Thinking Optional means "optional argument". Optional[int] only means the value may be None. The argument is still required unless you give it a default.
Using a value that may be None. The checker reports an error here, because s may be None:
def up(s: str | None) -> str: if s is None: # fix: rule out None first return "" return s.upper() print(repr(up(None))) # ''
Passing list[int] where list[float] is expected. The checker rejects it, because the function could add a float to your list of ints. If the function only reads the list, use Sequence[float], which accepts both.
isinstance with a generic. isinstance(x, list) works, but isinstance(x, list[int]) raises TypeError:
try: isinstance([1], list[int]) except TypeError as e: print("TypeError:", e) # TypeError: isinstance() argument 2 cannot be a parameterized generic
Old imports. from typing import List, Dict is the old style. Since Python 3.9, write list[int] and dict[str, int].
Expecting Python to stop wrong types. x: int = "5" runs fine. Only the checker complains, so run mypy or pyright as part of your work.
See also
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code did, as well as whether a test passed.
Found this useful? Share it with a friend who’s learning Python.