Type hints basics

Python · Practical · 12 min

Type hints document intent, power editor autocompletion and catch bugs before runtime in large codebases.

Type hints annotate what types a function expects and returns: def greet(name: str, times: int = 1) -> str:. Python itself ignores them at runtime — they are metadata. Their value comes from tools: a static type checker such as mypy or pyright, and editors that use them for completion and error highlighting.

The essentials for modern Python (3.10+ syntax):

  • built-in generics: list[int], dict[str, float], tuple[int, str], tuple[int, ...], set[str]
  • unions with |: int | str; optional values as str | None
  • collections.abc types for parameters: accept Iterable[str] or Sequence[int] or Mapping[str, int] rather than list/dict, so callers can pass any suitable object
  • typing.Any opts out of checking; object means "anything, but I won't assume its methods"
  • Callable[[int, str], bool] for functions

Annotate variables when the type isn't obvious: cache: dict[str, bytes] = {}. Annotating every local isn't necessary; checkers infer types from assignments.

Be liberal in what you accept and specific in what you return: take Iterable[str], return list[str].

For your own code, start by annotating public function signatures and run the checker in CI. reveal_type(x) (in typing since 3.11) makes the checker print what it infers, which is a great way to learn. Since annotations aren't enforced, use explicit validation for untrusted data such as user input or JSON.

Example

from collections.abc import Iterable, Callable

def average(values: Iterable[float]) -> float:
    items = list(values)
    return sum(items) / len(items) if items else 0.0

def find_user(users: dict[int, str], user_id: int) -> str | None:
    return users.get(user_id)

def apply(fn: Callable[[int], int], data: list[int]) -> list[int]:
    return [fn(x) for x in data]

print(average([1.0, 2.5, 4.0]))          # 2.5
print(average(x / 2 for x in range(5)))  # 1.0
name = find_user({1: "ana"}, 2)
if name is not None:                     # narrows str | None to str
    print(name.upper())
print(apply(lambda x: x * 10, [1, 2]))   # [10, 20]

print(average.__annotations__)           # hints are just metadata
print(find_user({1: "ana"}, "1"))        # None -- not checked at runtime

Remember

  • Hints are ignored at runtime; a checker like mypy/pyright enforces them
  • Use list[int], X | None, and collections.abc types
  • Accept abstract types (Iterable), return concrete ones (list)

Watch out for

  • Believing a hint like x: int makes Python reject a string at runtime.

Check yourself

  1. What happens at runtime when you call `def f(x: int) -> int` with a string?
  2. Which parameter type lets callers pass a list, tuple or generator of strings?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/typing.html