When you pass a list of user IDs to a function, how does the function know they should be integers? Collection type hints like list[int] and dict[str, User] make data structures self-documenting and catch type mismatches before they cause runtime errors.

Abstract Collection Types

From typing:

  • Sequence[T] - read-only-ish sequence interface (works for list, tuple, etc.)
  • Mapping[K, V] - read-only-ish mapping interface (works for dict)
  • Iterable[T] - any iterable input
list.py
# list[T]

# Typed list
names: list[str] = 
print("names:", names)

# Append/extend
names.append("Dora")
names.extend(["Eve", "Frank"])
print("after append/extend:", names)

# Indexing and slicing
first: str = names[0]
last_two: list[str] = names[-2:]
print("first:", first)
print("last_two:", last_two)

# List of numbers
scores: list[int] = [10, 20, 15]
print("scores:", scores)
print("sum:", sum(scores))

# list[T]

# Typed list
names: list[str] = 
print("names:", names)

# Append/extend
names.append("Dora")
names.extend(["Eve", "Frank"])
print("after append/extend:", names)

# Indexing and slicing
first: str = names[0]
last_two: list[str] = names[-2:]
print("first:", first)
print("last_two:", last_two)

# List of numbers
scores: list[int] = [10, 20, 15]
print("scores:", scores)
print("sum:", sum(scores))

# list[T]

# Typed list
names: list[str] = 
print("names:", names)

# Append/extend
names.append("Dora")
names.extend(["Eve", "Frank"])
print("after append/extend:", names)

# Indexing and slicing
first: str = names[0]
last_two: list[str] = names[-2:]
print("first:", first)
print("last_two:", last_two)

# List of numbers
scores: list[int] = [10, 20, 15]
print("scores:", scores)
print("sum:", sum(scores))

dict.py
# dict[K, V]

# Typed dict
ages: dict[str, int] = {"Alice": 30, "Bob": 27}
print("ages:", ages)

# Insert/update
ages["Charlie"] = 40
ages["Alice"] = ages["Alice"] + 1
print("after update:", ages)

# Lookup with get
maybe_age: int | None = ages.get("Dora")
print("ages.get('Dora'):", maybe_age)

# Iteration
for name, age in ages.items():
    print(f"  {name} -> {age}")

set_tuple.py
# set[T] and tuple[...]

# set[str]
tags: set[str] = {"python", "typing", "python"}
print("tags:", tags)

# set operations
more: set[str] = {"docs", "typing"}
print("union:", tags | more)
print("intersection:", tags & more)

# tuple with fixed length/types
point: tuple[float, float] = (1.5, 2.0)
print("point:", point)

# tuple with variable length
numbers: tuple[int, ...] = (1, 2, 3, 4)
print("numbers:", numbers)

nested.py
# Nested collection types

# list[dict[str, int]]
rows: list[dict[str, int]] = [
    {"id": 1, "score": 10},
    {"id": 2, "score": 30},
    {"id": 3, "score": 20},
]
print("rows:", rows)

# Compute aggregate
scores: list[int] = [r["score"] for r in rows]
print("scores:", scores)
print("avg score:", sum(scores) / len(scores))

# dict[str, list[str]]
groups: dict[str, list[str]] = {
    "admins": ["Alice", "Bob"],
    "users": ["Charlie"],
}
print("groups:", groups)

# Safe updates
new_user: str = "Dora"
groups.setdefault("users", []).append(new_user)
print("after add:", groups)

abstract_types.py
# Abstract collection types

from typing import Iterable, Mapping, Sequence

# Prefer abstract types in parameters

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


def first_item(values: Sequence[str]) -> str:
    return values[0]


def describe(mapping: Mapping[str, int]) -> str:
    parts = [f"{k}={v}" for k, v in mapping.items()]
    return ", ".join(parts)

print("total([1,2,3]) =", total([1, 2, 3]))
print("total((1,2,3)) =", total((1, 2, 3)))

print("first_item(['a','b']) =", first_item(["a", "b"]))
print("first_item(('x','y')) =", first_item(("x", "y")))

print("describe({'a':1,'b':2}) =", describe({"a": 1, "b": 2}))

Prefer abstract types for function parameters when you only need a subset of behavior.

generic type - a parameterized type like `list[T]` where `T` specifies the element type
tuple type - fixed-length `tuple[T1, T2]` for heterogeneous data, or `tuple[T, ...]` for variable-length
abstract types - types like `Sequence[T]` and `Mapping[K, V]` that accept multiple concrete types

Exercise: practical.py

Type a function that processes nested configuration data