Annotations like def score(doc: str, k: int = 5) -> list[float]: are not enforced by the interpreter. Static checkers (mypy, pyright) read them and flag mismatches before you run anything. Your editor uses them for autocompletion.
Useful types: list[str], dict[str, int], X | None (optional), Literal['low', 'high'], Callable[[str], int], TypedDict for dict shapes, and Protocol for structural interfaces (duck typing with checking).
In AI code, hints pay off twice: they make data flowing between pipeline stages explicit, and libraries like Pydantic and FastAPI use them at runtime to validate and to generate JSON Schema.
from typing import Literal, Protocol
class Embedder(Protocol):
def embed(self, texts: list[str]) -> list[list[float]]: ...
def search(q: str, embedder: Embedder, k: int = 5,
mode: Literal["dense", "hybrid"] = "dense") -> list[str]:
...Going deeper
Generics let containers stay precise: def first[T](xs: list[T]) -> T (3.12 syntax) or TypeVar. TypedDict describes JSON-like dicts; Literal and Enum describe fixed choices; Annotated[int, Field(gt=0)] attaches metadata that Pydantic and FastAPI read.
Run mypy or pyright in CI with --strict on new code. Gradual typing means you can start with your public interfaces and tighten over time.
Common pitfalls
- Assuming hints validate input at runtime. They don't, unless a library like Pydantic reads them.
Best resources for this lesson
- Docsmypy type hints cheat sheet
- DocsStatic typing with Python (typing.python.org) · official typing guides and spec
- ArticlePython type checking guide · Real Python