Back to Python tutorials
Intermediate16 min read

Type Hints & mypy

Add static typing with annotations, generics, Protocols, and mypy or pyright checking.

Type Annotations

Annotations document expected types for functions, variables, and class attributes. Python ignores them at runtime but checkers validate consistency.

Use list[str], dict[str, int] built-in generics in Python 3.9+. Optional[X] or X | None marks nullable values.

Gradual typing lets you annotate new code without retrofitting entire legacy modules immediately.

  • Enable from __future__ import annotations for forward refs
  • Run mypy in CI on typed packages
  • Avoid Any unless interfacing with untyped third-party code
def find_user(users: list[User], email: str) -> User | None:
    for user in users:
        if user.email == email:
            return user
    return None

Protocols and TypedDict

typing.Protocol defines structural interfaces—if it has read(), it is Readable without inheritance.

TypedDict models dict shapes with required and optional keys. NamedTuple and dataclasses complement static typing for records.

Generics TypeVar enable reusable containers like Repository[T] with checker enforcement.

  • Use @runtime_checkable Protocol sparingly for isinstance
  • Align pydantic models with TypedDict for API contracts
  • Document variance on generic containers when advanced
class SupportsClose(Protocol):
    def close(self) -> None: ...

def cleanup(resource: SupportsClose) -> None:
    resource.close()

Running mypy

mypy reads pyproject.toml [tool.mypy] for strictness flags: disallow_untyped_defs, warn_return_any. Start permissive and tighten per package.

Type stubs in types-* packages annotate untyped libraries. Local stub files (.pyi) cover internal C extensions.

Pyright in VS Code offers fast feedback; mypy remains common in CI pipelines.

  • Fix typing in libraries before application code
  • Use cast() only when checker cannot infer safe narrowing
  • Combine pydantic validation with static types at boundaries
# pyproject.toml
[tool.mypy]
python_version = "3.12"
strict = true

Get In Touch


Ready to discuss your next project? Drop me a message.