ML Algorithms
Python
26 / 29

Python Typing

Make Python feel like a statically-typed language — catch bugs before they ship.

Why bother?
Type hints don't affect runtime speed, but they catch entire classes of bugs in CI, power autocomplete, and make refactors safe. Every serious codebase uses them.

1. The Basics

def greet(name: str, times: int = 1) -> str:
    return ("Hello, " + name + "!\n") * times

ages: dict[str, int] = {"alice": 30}
scores: list[float] = [0.91, 0.88]
maybe_user: str | None = None        # PEP 604 union (3.10+)

2. Generics

Write functions and containers that preserve the input type. Python 3.12+ uses the clean[T] syntax.

def first[T](items: list[T]) -> T | None:
    return items[0] if items else None

x: int       = first([1, 2, 3])         # inferred as int
y: str | None = first(["a", "b"])       # inferred as str | None

class Cache[K, V]:
    def __init__(self) -> None:
        self._data: dict[K, V] = {}
    def get(self, key: K) -> V | None:
        return self._data.get(key)

3. Protocols (Structural Typing)

A Protocol defines a shape. Anything with the right methods satisfies it — no inheritance required. This is Python's version of Go interfaces.

from typing import Protocol

class Estimator(Protocol):
    def fit(self, X, y) -> None: ...
    def predict(self, X) -> list: ...

def cross_val(model: Estimator, X, y) -> float:
    model.fit(X, y)
    return score(model.predict(X), y)

# Works with sklearn, xgboost, your custom class — anything with .fit/.predict

4. TypedDict & Literal

from typing import TypedDict, Literal

class User(TypedDict):
    id: int
    name: str
    role: Literal["admin", "member", "guest"]

def grant(u: User, perm: Literal["read", "write"]) -> None: ...

5. Useful Tools

  • Optional[X] = X | None
  • Callable[[int, str], bool] — function signatures
  • Final, ClassVar — immutability markers
  • NewType("UserId", int) — distinct types from primitives
  • cast(T, x) — escape hatch when the checker can't infer

6. Static Checking in CI

# pyproject.toml
[tool.mypy]
strict = true
python_version = "3.12"

# CI step
$ mypy src/                # fails the build on type errors
$ pyright src/             # Microsoft's faster alternative
Pragmatic rules
Type all public functions. Use Any sparingly and only at boundaries. Prefer Protocols over abstract base classes for new code. Enable strictmode from day one — it's painful to retrofit.