Type Hints and Docstrings
What You'll Learn
How to write docstrings that explain what code does, and type hints that describe what data it expects and returns.
Why Document Code?
Without documentation:
- You'll forget what a function does in 2 weeks
- Teammates waste time reverse-engineering intent
- IDEs can't give useful auto-complete
- Type checkers can't find bugs
With documentation:
help(function)shows useful information- IDEs show parameter types while you type
mypyfinds type errors before runtime- Bugs are caught earlier, reviews are faster
Docstrings
A docstring is a string literal at the start of a module, class, or function. Python stores it in __doc__:
def calculate_bmi(weight_kg: float, height_m: float) -> float:
"""
Calculate Body Mass Index (BMI).
Args:
weight_kg: Body weight in kilograms (must be positive).
height_m: Height in metres (must be positive, non-zero).
Returns:
BMI value as a float. Normal range: 18.5–24.9.
Raises:
ValueError: If height_m is zero or negative.
Example:
>>> calculate_bmi(70, 1.75)
22.857142857142858
"""
if height_m <= 0:
raise ValueError(f"height_m must be positive, got {height_m}")
return weight_kg / (height_m ** 2)
Access docstrings:
help(calculate_bmi)
print(calculate_bmi.__doc__)
One-Line Docstrings
For simple functions, one line is enough:
def double(n: int) -> int:
"""Return n multiplied by 2."""
return n * 2
def is_even(n: int) -> bool:
"""Return True if n is even."""
return n % 2 == 0
Class Docstrings
from dataclasses import dataclass
@dataclass
class Product:
"""
Represents a product in the catalog.
Attributes:
name: Product display name.
price: Price in USD (must be >= 0).
in_stock: Whether the product is available.
"""
name: str
price: float
in_stock: bool = True
def apply_discount(self, rate: float) -> "Product":
"""
Return a new Product with a discounted price.
Args:
rate: Discount rate between 0.0 and 1.0.
Returns:
New Product with reduced price.
Raises:
ValueError: If rate is outside 0.0–1.0 range.
"""
if not 0.0 <= rate <= 1.0:
raise ValueError(f"rate must be 0.0–1.0, got {rate}")
return Product(self.name, self.price * (1 - rate), self.in_stock)
Type Hints — Complete Reference
Primitives
x: int = 5
y: float = 3.14
name: str = "Alice"
active: bool = True
nothing: None = None
Collections (Python 3.9+ — use built-in types)
items: list[str] = ["a", "b", "c"]
pair: tuple[int, int] = (1, 2)
any_tuple: tuple[int, ...] = (1, 2, 3, 4) # variable-length tuple
tags: set[str] = {"x", "y"}
config: dict[str, int] = {"port": 5432}
Optional and Union
# Python 3.10+ (preferred)
def find(id: int) -> str | None:
...
# Python 3.9 and earlier
from typing import Optional
def find(id: int) -> Optional[str]:
...
# Multiple types
from typing import Union
value: int | float | str # Python 3.10+
value: Union[int, float] # older syntax
Callable
from typing import Callable
# A function that takes (int, str) and returns bool
handler: Callable[[int, str], bool]
# A function with no args that returns None
callback: Callable[[], None]
Generic Collections
from typing import Iterator, Generator, Sequence, Mapping
def get_names() -> Iterator[str]:
yield "Alice"
yield "Bob"
def process(items: Sequence[int]) -> None: # accepts list, tuple, etc.
...
def lookup(data: Mapping[str, int]) -> None: # accepts dict, etc.
...
TypeAlias — Name Complex Types
from typing import TypeAlias
# Instead of repeating complex types
UserRecord: TypeAlias = dict[str, str | int | list[str]]
def load_user(user_id: int) -> UserRecord:
...
Type Checking with mypy
mypy checks type hints without running your code:
pip install mypy
mypy src/main.py
mypy src/ # check entire directory
Example: catching a bug before it crashes:
def greet(name: str) -> str:
return "Hello, " + name
greet(42) # mypy: error: Argument 1 has incompatible type "int"; expected "str"
Doctest — Examples That Double as Tests
def add(a: int, b: int) -> int:
"""
Add two integers.
>>> add(2, 3)
5
>>> add(-1, 1)
0
>>> add(0, 0)
0
"""
return a + b
Run doctests:
python3 -m doctest module.py -v
Documentation Styles
Choose one and be consistent:
Google style (most common in Python):
def fn(x: int, y: str) -> bool:
"""Short description.
Args:
x: Description of x.
y: Description of y.
Returns:
True if successful.
Raises:
ValueError: If x is negative.
"""
NumPy style (common in scientific code):
def fn(x, y):
"""
Short description.
Parameters
----------
x : int
Description of x.
y : str
Description of y.
Returns
-------
bool
True if successful.
"""
What to Always Document
| Always document | Optional |
|---|---|
| Public functions/methods | Private helpers (if obvious) |
| Non-obvious parameters | Trivial one-liners |
| What exceptions are raised | Internal variables |
| Return value meaning | Implementation details |
Quick Reference
# One-line docstring
def fn():
"""Return the result of calculation."""
# Full docstring
def fn(x: int) -> str:
"""
Short summary.
Args:
x: What x is.
Returns:
What the function returns.
Raises:
ValueError: When and why.
Example:
>>> fn(5)
'five'
"""
# Type hints
param: type
param: type | None
param: list[str]
param: dict[str, int]
-> return_type
# Run type checker
# mypy src/
# Run doctests
# python3 -m doctest module.py