Type Hints

This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A type hint documents the expected type of a variable, parameter, or return value. Python never checks these at runtime — they exist for readers and for external tools.

Why Type Hints, and Basic Annotations

Type hints improve documentation and enable tooling (autocomplete, refactoring, static analysis) — they are not enforced by the interpreter. This distinction is explained in the typing module documentation and formalized in PEP 484, which introduced the syntax:

x: int = 5
name: str = "Ada"

def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()

# Hints are pure documentation at runtime -- this "wrong" call still executes fine:
print(greet(42))   # 'Hello, 42!' -- name violates the `str` hint, but nothing checks it

The interpreter never inspects x: int or def greet(name: str, times: int = 1) → str to reject mismatched values — see Static Checkers: mypy and pyright below for how a separate tool catches this instead.

Generics: list[int], dict[str, int], and Unions

Since Python 3.9, built-in collections are subscriptable directly — list[int], dict[str, int], tuple[int, str] — without importing List/Dict from typing. Full generic-alias coverage is in Generic Alias Types:

def total(values: list[int]) -> int:
    return sum(values)

def counts(words: list[str]) -> dict[str, int]:
    result: dict[str, int] = {}
    for word in words:
        result[word] = result.get(word, 0) + 1
    return result

Modern unions: X | None (3.10+)

Since Python 3.10, | builds a union type directly, per typing.Union:

def find(items: list[str], target: str) -> int | None:
    for i, item in enumerate(items):
        if item == target:
            return i
    return None    # int | None covers this

Older code: Optional[X] and Union[X, Y]

Before 3.10 (or for compatibility with older code), use Optional and Union from typing, documented at typing.Optional:

from typing import Optional, Union

def find(items: list, target) -> Optional[int]:      # same as int | None
    for i, item in enumerate(items):
        if item == target:
            return i
    return None

def parse(value: Union[str, int]) -> str:             # same as str | int
    return str(value)

TypeVar, Generic Functions and Classes, and Protocol

TypeVar for generic functions

A TypeVar lets a function’s parameter and return type vary together while staying consistent across one call — documented at typing.TypeVar:

from typing import TypeVar

T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

first([1, 2, 3])          # T is int; returns an int
first(["a", "b"])         # T is str; returns a str

Generic classes

A class can be generic over the same TypeVar, documented under typing.Generic:

from typing import Generic, TypeVar

T = TypeVar("T")

class Box(Generic[T]):
    def __init__(self, item: T) -> None:
        self.item = item

    def get(self) -> T:
        return self.item

int_box: Box[int] = Box(42)
str_box: Box[str] = Box("hello")

Protocol for structural typing

A Protocol describes an interface by shape — any object with matching methods satisfies it, with no explicit inheritance required ("duck typing" made checkable). See typing.Protocol:

from typing import Protocol

class SupportsClose(Protocol):
    def close(self) -> None: ...

class FileHandle:
    def close(self) -> None:
        print("closed")

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

shut_down(FileHandle())   # OK: FileHandle has a matching close() method

Callable[[int], str]

Callable[[ArgTypes], ReturnType] annotates a function value, documented at typing.Callable:

from typing import Callable

def apply(func: Callable[[int], str], value: int) -> str:
    return func(value)

apply(lambda n: f"#{n}", 7)   # "#7"

Static Checkers: mypy and pyright

Type hints are read by opt-in static checkers such as mypy and pyright — they analyze source without running it and report mismatches the interpreter itself never catches. Install and run mypy from the command line:

pip install mypy
mypy my_module.py

Given the earlier greet(name: str, times: int = 1) → str example, mypy flags the misordered call at analysis time, before the program ever runs:

$ mypy my_module.py
my_module.py:10: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)

Because hints carry no runtime effect (see Why Type Hints, and Basic Annotations above), skipping mypy/pyright means Python happily executes the same mismatched call — the checker is the only thing enforcing the contract the annotations describe. Background and the annotation object model itself (annotations, string vs. evaluated forms) are covered in the Annotations Best Practices HOWTO.

See Also

  • Dataclasses and Enums — typed field declarations on dataclass-decorated classes.

  • Functions — the parameter styles that function annotations attach to.