Type Hints and Static Analysis
Add useful type annotations, model optional and union values, define structural interfaces with Protocol, and understand what type checkers can and cannot prove.
Before this lesson
Annotate common Python APIs
Model optional values precisely
Use Protocol for structural interfaces
The short answer
Python type hints document expected shapes and enable static tools without changing normal runtime semantics. Annotate public boundaries first, prefer precise domain types, and combine static checks with runtime validation and tests.
Build the mental model
Annotations describe parameter and return expectations for readers, editors, and tools such as pyright or mypy. Modern built-in generics use list[str] and dict[str, int]. T | None means either T or None and requires explicit handling.
The central idea in this lesson is executable documentation. Read the rule, predict a concrete outcome, and then run the smallest example that can confirm or reject the prediction. This separates knowledge from familiarity with syntax.
Make the design choice explicit
A Protocol describes required behavior without forcing inheritance. This supports dependency inversion and test doubles. TypeAlias and NewType can clarify domain meaning, but names should reduce ambiguity rather than decorate every primitive.
from typing import Protocol
class PriceSource(Protocol):
def price_for(self, code: str) -> float: ...
def invoice_total(codes: list[str], source: PriceSource) -> float:
return sum(source.price_for(code) for code in codes)
print("Type hints describe the contract; tests verify behavior.")Expected output
Type hints describe the contract; tests verify behavior.
Trace the example before running it. Identify each input, transformation, returned value, and side effect. Then change one boundary value and explain why the new behavior follows from the rule rather than memorizing the output.
Recognize failure modes
Hints are not automatic runtime validation. Any can silence useful checks and should be contained at untyped boundaries. Avoid excessively clever generic types that make a simple function harder for the team to understand.
Use a professional practice loop
Turn the concept into a repeatable workflow: write down the expected behavior, implement one coherent change, run a representative example, and retain a regression check. If the result surprises you, capture the exact input and error before changing anything.
Review the program for names, boundaries, and hidden side effects. A solution is complete when another developer can understand its contract, reproduce its setup, and verify both the successful path and one meaningful failure path.
Quick knowledge check
Answer before you reveal.
01Do annotations reject wrong values at runtime by default?
No. They support documentation and static analysis; runtime validation remains a separate concern.
02What four steps make the practice loop reliable?
Predict the behavior, make one coherent change, run it, and retain a check that detects regression.
Exercise
Practice challenge
Annotate an order-pricing module, represent missing discounts explicitly, and define a Protocol for the price repository dependency.
Requirements
- The successful path produces the documented result
- At least one boundary or invalid case is handled deliberately
- Calculation or domain logic is separated from console interaction
Optional extension: Add one automated regression check for the most important rule.
Open in Python compilerLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
Is executable documentation only important in large programs?
No. Small programs reveal the same rules with less noise, and learning the rule early prevents fragile habits from becoming architecture.
Should I memorize every API used here?
No. Memorize the mental model and how to verify behavior. Use documentation for exact names and parameters when needed.
How do I know the exercise is finished?
Meet every success criterion, test at least one boundary or failure case, and explain why the output follows from the code.