Type hints describe what a function accepts and returns. Python ignores them at runtime, but a type checker such as mypy (or your editor) reads them and reports mistakes like passing a str where an int is expected.
Common hints: list[int], dict[str, float], tuple[str, int], X | None for optional values, Literal["pending", "paid"] for fixed options, and TypedDict for dictionaries with known keys.
Add hints gradually — start with function signatures. Run mypy in your project (and in CI) the same way you run tests.
from typing import Literal, TypedDict
Status = Literal["pending", "paid", "failed"]
class Payment(TypedDict):
id: int
amount: int
status: Status
phone: str | None
def total_paid(payments: list[Payment]) -> int:
return sum(p["amount"] for p in payments if p["status"] == "paid")
def find(payments: list[Payment], payment_id: int) -> Payment | None:
for p in payments:
if p["id"] == payment_id:
return p
return None
payments: list[Payment] = [
{"id": 1, "amount": 15_000, "status": "paid", "phone": "0712345678"},
{"id": 2, "amount": 8_000, "status": "pending", "phone": None},
]
print(total_paid(payments))
found = find(payments, 2)
if found is not None: # mypy forces this check
print(found["status"])pip install mypy
mypy --strict typed.pyKey points
- Hints are checked by tools, not by Python at runtime.
X | Noneforces you to handle the missing case.- Run mypy regularly — it catches bugs tests may miss.
Exercise
Add full type hints to your Library exercise from the Classes lesson and run mypy --strict on it until it reports no errors.
Show solution
Try the exercise yourself first — then compare your approach with this one.
With every attribute, parameter and return value annotated, mypy --strict checks the whole module. Note Book | None for the lookup — mypy then forces the None case to be handled before using the book.
from dataclasses import dataclass
class BookAlreadyBorrowedError(Exception):
pass
@dataclass
class Book:
isbn: str
title: str
author: str
available: bool = True
class Library:
def __init__(self) -> None:
self._books: dict[str, Book] = {}
def add(self, book: Book) -> None:
self._books[book.isbn] = book
def find(self, isbn: str) -> Book | None:
return self._books.get(isbn)
def borrow(self, isbn: str) -> Book:
book = self.find(isbn)
if book is None:
raise KeyError(f"no book with ISBN {isbn}")
if not book.available:
raise BookAlreadyBorrowedError(book.title)
book.available = False
return book
def available_books(self) -> list[str]:
return [b.title for b in self._books.values() if b.available]
library = Library()
library.add(Book("978-1", "Things Fall Apart", "Chinua Achebe"))
print(library.borrow("978-1").title, library.available_books())mypy --strict typed_library.py
# Success: no issues found in 1 source file