Advanced · Lesson 16 of 18 · 18 min

The type system

Annotations, gradual typing, and what Python does not check.

  • Annotate functions and data structures
  • Use Optional, Union, TypeVar, and Protocol
  • Know that CPython ignores annotations at runtime

Hints are for people and checkers

Python's types are gradual and optional. `def greet(name: str) -> str:` documents intent. CPython will still happily call `greet(42)` unless you run a checker (Pyright, mypy) or a runtime validator. Annotations are available as `typing.get_type_hints` and, from 3.10, can use `|` unions: `str | None`.

Start with public function signatures and data structures. Do not type every local. Prefer built-in generics: `list[int]`, `dict[str, float]`, `tuple[str, ...]`. `Any` is an escape hatch that disables checking — use it as a boundary, not a habit.

Modern annotations
from typing import Sequence

def longest(items: Sequence[str]) -> str | None:
    if not items:
        return None
    return max(items, key=len)

print(longest(["a", "alpha", "ab"]))
print(longest([]))
print(longest.__annotations__)

Protocols are structural

A `Protocol` describes a shape: any object with those methods matches, without inheritance. This is typing for duck typing. `TypeVar` names a consistent placeholder so `def first(xs: list[T]) -> T` keeps the element type.

`TypedDict` types dictionaries with known keys. `Literal['asc', 'desc']` restricts to specific values. `cast` is a checker-only assertion — it does nothing at runtime.

Protocol and TypeVar
from typing import Protocol, TypeVar

T = TypeVar("T")

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

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

def shutdown(resource: Closeable) -> None:
    resource.close()

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

shutdown(Handle())
print(first([10, 20]), first(["a", "b"]))

Check yourself

Quiz

  1. 1.If you call a function with the wrong annotated type at runtime:

  2. 2.`str | None` means:

  3. 3.A Protocol matches:

Practice

Exercise

Write `pick(items: list[str], index: int) -> str | None` that returns the item at index, or None if the index is out of range. Negative indexes should work like Python lists.

Tutor

Ask a question about this lesson. Answers are generated on demand and capped so the session stays focused.