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

Python Type Hints Cheat Sheet

1 · The lesson

read

A 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 ->.

python
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.

HintMeans
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.

python
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.

python
from collections.abc import Callable

def run(f: Callable[[int], int]) -> int:
    return f(1)

print(run(lambda n: n + 41))   # 42
HintMeans
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:

python
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.

python
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

python
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.

python
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:

python
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.

python
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

python
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

python
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:

python
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:

python
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:

python
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.in

Short 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.