Type Hints & Generics
Annotate code for clarity and tooling — from simple hints to Generic, TypedDict and the type statement.
Type hints describe the types your code expects. Python does not enforce them at runtime — they are for humans and tools: editors autocomplete, mypy catches mismatches before you run, and readers understand your intent at a glance.
The modern, import-light style
Since Python 3.10 you write unions with | and use built-in generics directly — no typing.List or typing.Optional needed:
def greet(names: list[str], greeting: str | None = None) -> str:
greeting = greeting or 'Hello'
return ', '.join(f'{greeting}, {n}' for n in names)
print(greet(['Ada', 'Grace'])) # Hello, Ada, Hello, GraceRicher building blocks
TypedDict— a dict with a known set of typed keys, great for JSON-shaped data.- Generic functions/classes —
def first[T](xs: list[T]) -> T(3.12+ syntax). type Alias = ...— the 3.12typestatement names a complex hint.Callable[[int], str]— describe a function you accept as a parameter.
Hints are optional and incremental: add them where they clarify — public function signatures, data shapes, tricky returns — and skip them where the code is already obvious.
Example
When to use it
- A public library annotates all function signatures so IDEs provide accurate autocomplete for users.
- A mypy check in CI catches a function that accidentally returns None where the annotation says str.
- A TypedDict describes the shape of a JSON API response so mypy can validate field access throughout the codebase.
More examples
TypedDict for structured dicts
Defines a TypedDict to type-check dict accesses, giving mypy visibility into dict key types.
from typing import TypedDict
class UserRecord(TypedDict):
name: str
age: int
email: str
def display(user: UserRecord) -> str:
return f"{user['name']} ({user['age']})"
record: UserRecord = {'name': 'Alice', 'age': 30, 'email': '[email protected]'}
print(display(record)) # Alice (30)Generic function with TypeVar
Uses TypeVar so the return type matches the list element type, preserving type information for callers.
from typing import TypeVar, List
T = TypeVar('T')
def first(items: List[T]) -> T | None:
return items[0] if items else None
print(first([1, 2, 3])) # 1
print(first(['a', 'b'])) # a
print(first([])) # NoneCallable and Union types
Annotates a higher-order function with Callable and a nullable return with Union[int, None].
from typing import Callable, Union
def apply(fn: Callable[[int], int], value: int) -> int:
return fn(value)
def maybe_square(x: Union[int, None]) -> Union[int, None]:
return x ** 2 if x is not None else None
print(apply(lambda n: n * 2, 5)) # 10
print(maybe_square(None)) # None
print(maybe_square(4)) # 16
Discussion