Python typing
Types are a gradual tool, not a moral obligation. Annotate the boundaries where wrong shapes cause real bugs, pick the construct that states the contract, and set a checker strictness the team can keep green.
Method
- Type the edges first. Public function signatures, module boundaries, and data crossing I/O earn annotations because callers rely on them and the checker verifies both sides. Local variables mostly infer; annotate a local only when inference is wrong or the intent is unclear.
- Choose Protocol for behavior, ABC for a class family. A
Protocoldescribes "anything with these methods" and needs no inheritance, so it fits duck-typed seams and third-party objects you cannot subclass. An ABC fits a closed hierarchy you own and want to share implementation or force registration. Prefer Protocol for function parameters. - Reach for generics when a container or function preserves a type.
def first(xs: Sequence[T]) -> Tandclass Box(Generic[T])keep the caller's type flowing through. UseTypeVarbounds (T: Comparable) to constrain, and modernclass Box[T]:syntax on 3.12+. Do not add a generic where a concrete type would do. - Model structured dicts with TypedDict, not
dict[str, Any]. JSON payloads and config records get aTypedDictso the checker catches missing keys and wrong value types. Mark optional keys withNotRequired. When the shape is a real object with methods, use a dataclass instead; see python-dataclasses. - Set strictness explicitly and ratchet it. Start mypy or pyright in a
lenient mode on legacy code, then raise the floor: enable
disallow_untyped_defs,warn_return_any, andstrictper package as coverage grows. Pin the checker version in the lockfile so CI and local agree. Treat new errors as build failures, not warnings. - Use escape hatches sparingly and visibly.
cast,# type: ignorewith a specific error code, andAnyare admissions of a gap; each needs a reason nearby.Anyis contagious, so isolate it at the boundary and convert to a precise type immediately.
Boundaries
- Type hints are not enforced at runtime. For runtime validation of external data use a validating library, not annotations alone.
- Do not chase 100 percent coverage on throwaway scripts or fast-moving prototypes; the annotation cost outruns the payoff there.
- Overloads and deep generic gymnastics can make a signature unreadable. If the types are harder to follow than the code, simplify the API instead.