Typing collections

typing · Essentials · 9 min

Most real data is collections; precise element types catch the bugs that matter.

Since Python 3.9, built-in collection types accept type arguments directly:

  • list[int], set[str], dict[str, float],
  • tuple[int, str] for a fixed-length pair, tuple[int, ...] for any length of ints.

The old typing.List, typing.Dict aliases are deprecated; use the built-ins.

For parameters, prefer abstract types from collections.abc that describe only what you need:

  • Iterable[T] if you just loop over it,
  • Sequence[T] if you index or take len() (lists, tuples, strings),
  • Mapping[K, V] for read-only dict-like access,
  • MutableSequence, MutableMapping if you modify it.

This makes functions accept more inputs (a tuple, a generator, a custom class) and documents that you don't mutate the argument. For return types, be concrete (list[str]) so callers know exactly what they get.

An empty literal needs a hint, or the checker can't know the element type: names: list[str] = [].

Sequence[str] has a trap: a single str is itself a sequence of strings, so passing "abc" where a list of names is expected type-checks.

Example

from collections.abc import Iterable, Mapping, Sequence

def total(prices: Iterable[float]) -> float:
    return sum(prices)

def first_last(items: Sequence[str]) -> tuple[str, str]:
    return items[0], items[-1]

def lookup(table: Mapping[str, int], key: str) -> int:
    return table.get(key, 0)

print(total([1.5, 2.5]), total(p for p in (1.0, 2.0)))   # 4.0 3.0
print(first_last(("a", "b", "c")))                       # ('a', 'c')
print(lookup({"x": 1}, "y"))                             # 0

scores: dict[str, list[int]] = {}
scores.setdefault("ada", []).append(95)
point: tuple[int, int] = (3, 4)
coords: tuple[float, ...] = (1.0, 2.0, 3.0)
print(scores, point, coords)

Remember

  • Use list[int], dict[str, int], tuple[int, ...] directly
  • Accept abstract types (Iterable, Sequence, Mapping); return concrete ones
  • Annotate empty collections

Watch out for

  • Typing a parameter as list[str] when any iterable would do, forcing callers to convert.

Check yourself

  1. Which annotation is best for a parameter you only loop over once?

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#generics