Python Typing¶
Type hints make Python code self-documenting and catch whole classes of bugs before they run. This sheet covers annotations, generics, static checking with mypy, and typing patterns that work well in data science code.
Table of Contents¶
- Installation / Setup
- Core Syntax
- Common Operations
- Practical Examples
- Common Patterns
- Best Practices
- Common Mistakes
- Troubleshooting
- Related Cheat Sheets
- Official Documentation
Installation / Setup¶
Type hints work in any Python 3.x with zero setup. Add a checker for real value:
pip install mypy # or: uv add --dev mypy
mypy src/ # check a directory
mypy model.py --strict # strict mode: no untyped defs
Alternatives: pyright (fast, VS Code/Pylance use it), pytype (Google).
Core Syntax¶
Basic annotations¶
def accuracy(y_true: list[int], y_pred: list[int]) -> float:
return sum(a == b for a, b in zip(y_true, y_pred)) / len(y_true)
def load(path: str, limit: int | None = None) -> list[dict[str, float]]:
...
| Annotation | Meaning |
|---|---|
int, str, float, bool, bytes |
Built-ins (lowercase!) |
list[int], dict[str, int], set[str], tuple[int, ...] |
Parameterized built-ins (3.9+) |
int \| None |
Union shorthand (3.10+); pre-3.10: Optional[int] |
Any |
Anything goes — use sparingly |
Literal["train", "eval"] |
Exact allowed values |
Callable[[int, int], int] |
Function signature |
Iterable[int] / Sequence[int] / Mapping[str, int] |
Abstract containers |
TypeVar("T") |
Generic placeholder |
TypedDict |
Dict with fixed keys and types |
Protocol |
Structural interface (duck typing) |
NewType("UserId", int) |
Distinct type at check time |
Final, ClassVar |
Constants, class-level vars |
Annotated[int, ...] |
Extra metadata (e.g., pydantic constraints) |
-> None |
Function returns nothing |
Variables and classes¶
from dataclasses import dataclass
@dataclass
class Config:
lr: float = 0.01
epochs: int = 10
tags: list[str] = field(default_factory=list)
x: int = 5
y: list[float] = []
Generics¶
from typing import TypeVar
T = TypeVar("T")
def first(xs: list[T]) -> T: # works for list[int], list[str], ...
return xs[0]
from typing import Generic
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None: ...
def pop(self) -> T: ...
Protocols (structural typing)¶
from typing import Protocol
class HasMean(Protocol):
def mean(self) -> float: ...
def summarize(x: HasMean) -> float: # any object with .mean() passes
return x.mean()
Common Operations¶
Check types¶
# Runtime introspection
from typing import get_type_hints
get_type_hints(accuracy) # {'y_true': list[int], ...}
TYPE_CHECKING for circular imports¶
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from data_loader import Loader # never imported at runtime
Practical Examples¶
1. Typed data pipeline¶
from typing import TypedDict
class Row(TypedDict):
user_id: int
score: float
tags: list[str]
def load_rows(path: str) -> list[Row]:
import json
return json.loads(Path(path).read_text(encoding="utf-8"))
rows: list[Row] = load_rows("data.json")
rows[0]["score"] # float — checker knows
rows[0]["missing"] # mypy error: key not in Row
2. Typed NumPy/pandas interop¶
import numpy as np
import pandas as pd
def normalize(x: np.ndarray) -> np.ndarray:
return (x - x.mean()) / x.std()
def top_features(df: pd.DataFrame, k: int) -> pd.DataFrame:
return df.abs().sum().sort_values(ascending=False).head(k).to_frame()
Add stubs to make the checker happy: pip install pandas-stubs (NumPy ships its own types).
3. Generic training function¶
from typing import TypeVar, Protocol
T = TypeVar("T")
class Trainer(Protocol):
def fit(self, X: np.ndarray, y: np.ndarray) -> None: ...
def predict(self, X: np.ndarray) -> np.ndarray: ...
def run(model: Trainer, X: np.ndarray, y: np.ndarray) -> np.ndarray:
model.fit(X, y)
return model.predict(X)
Common Patterns¶
# Optional input + explicit None check
def load(path: str | None = None) -> str:
path = path or "default.json"
...
# NewType for unit-like values
UserId = NewType("UserId", int)
def fetch(uid: UserId) -> ... # UserId(5), not plain 5
# Annotated metadata for pydantic
from typing import Annotated
from pydantic import Field
Age = Annotated[int, Field(ge=0, le=130)]
# Literal for enums-like choices
Split = Literal["train", "val", "test"]
def get_split(name: Split) -> pd.DataFrame: ...
Best Practices¶
- Annotate public function signatures — parameters and return types.
- Prefer
list[int]/dict[str, int]overList[int]/Dict(modern style). - Use
Anydeliberately, never as a default habit — it disables checking. - Use
Sequence/Iterable/Mappingfor parameters, concrete types for returns. - Add
pandas-stubsand prefernp.ndarrayannotations over plainobject. - Run mypy in CI — a
mypy.ini/pyproject.tomlconfig makes checks consistent. - Type dataclasses fully — they're the backbone of typed configs.
- Gradually annotate existing code;
--stricton new modules is fine.
Common Mistakes¶
| Mistake | Consequence | Correct approach |
|---|---|---|
List[int]/Dict from typing |
Deprecated-style noise (works, but legacy) | Built-ins: list[int], dict[str, int] |
Capitalized Int, Str |
Runtime errors (Int is a class, not a type) |
Lowercase built-ins |
Optional[int] where int \| None fits |
Old syntax (fine pre-3.10) | Use \| on 3.10+ |
| No stubs for pandas | mypy errors on every DataFrame | pip install pandas-stubs |
| Over-annotating internals | Noisy, hard to maintain | Annotate APIs, not every local |
NewType at runtime only |
No runtime validation | Pair with pydantic for real validation |
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
Missing type parameters for generic type "list" |
Used list without [...] |
list[int] |
Name "list" is not defined |
Python < 3.9 | Use typing.List or upgrade |
Cannot find implementation or library stub |
Third-party package untyped | --ignore-missing-imports or install stubs |
error: Returning Any from function declared to return "int" |
Value from untyped lib | Annotate the library boundary explicitly |
Attribute "mean" is unknown for "object" |
Annotated as object |
Annotate as np.ndarray or define a Protocol |
Related Cheat Sheets¶
- Python Essentials — functions and data structures to annotate
- Python OOP — generics,
Protocol,ClassVarin classes - Python Standard Library —
typingsits besidecollections/itertools - Data Analysis — pandas/NumPy annotations with stubs
- Home · Central Navigation Index
Official Documentation¶
Last Updated¶
2026-09-06 — Initial version.