Type hints: documenting types
Type hints say what a function takes and returns: line_total(quantity: int, unit_price: float) -> float. Python stores them but doesn't check them; tools such as mypy read them to catch mistakes before the program runs.
- Annotate parameters and return values, including
list[str],dict[str, float]andX | None - Show that hints are stored (
get_type_hints) but not enforced when the code runs - Use hints at run time on purpose, to check a record's field types
A function's name says what it does; its type hints say what goes in and out. quantity: int annotates a parameter, -> float the return value. Collections say what they hold: list[str], dict[str, float], tuple[str, int]. str | None means "a text, or None", which is how a lookup that can fail is written.
Hints are documentation that tools can read. Python itself stores them and otherwise ignores them: nothing checks them when the function is called. A static type checker such as mypy, which you run on your computer (mypy shop.py), reads the hints and reports calls that don't match before the program ever runs. Editors use them for completion and warnings. In this course's browser there is no type checker; what you can see here is what Python itself does.
Run each example to see what it prints. Running examples doesn't count toward progress.
from typing import get_type_hints def line_total(quantity: int, unit_price: float, discount: float = 0.0) -> float: return round(quantity * unit_price * (1 - discount), 2) def find_customer(customers: dict[str, str], customer_id: str) -> str | None: return customers.get(customer_id) print(line_total(2, 17.92), line_total(3, 10, 0.1)) print(find_customer({"C00077": "Jamal Walker"}, "C99999")) print(get_type_hints(line_total))
def order_total(prices: list[float]) -> float: return sum(prices) print(order_total([4.75, 12.5])) print(order_total(["4.75", "12.50"]))
Because hints are ordinary values you can read, a program can use them on purpose at run time. Data-validation libraries (pydantic, for example) are built on exactly that. Here is the core of the idea, for a dataclass: compare each field's value with its annotated type.
from dataclasses import dataclass, fields from typing import get_type_hints @dataclass class Payment: payment_id: str order_id: str amount: float instalments: int = 1 def type_problems(record) -> list[str]: hints = get_type_hints(type(record)) problems = [] for f in fields(record): value = getattr(record, f.name) expected = hints[f.name] if not isinstance(value, expected): problems.append(f"{f.name}: expected {expected.__name__}, got {type(value).__name__}") return problems good = Payment("PAY10231", "O100282", 35.84) bad = Payment("PAY10232", 100918, "55.72", 3) print(type_problems(good)) print(type_problems(bad))
When to use it: on functions other people (or you, later) call, and on every dataclass field. Hints pay off most in larger programs, together with a type checker in your editor or CI.
Mistakes to avoid: believing hints validate input (they don't; validate real input yourself); a return hint that lies (-> float on a function that can return None should be -> float | None); and list without the item type when the item type matters.
param: typeand-> typedocument a function;list[str],dict[str, float]andX | Nonedescribe collections and optional values.- Python stores hints (
get_type_hints) but doesn't check them; a static checker such as mypy does, before the program runs. - Hints are values, so code can read them on purpose, for example to validate a record.
- Next: describing dictionary shapes and interfaces with TypedDict and Protocol.