03 — Strict Typing, Generics, Protocol & Runtime Boundaries
At a glance
After completing this lesson you can:
- Design decoupled system interfaces using
typing.Protocol(structural subtyping / static duck typing). - Write generic repository and data structures (
TypeVar,Generic[T]). - Validate data at untrusted runtime boundaries while preserving static type safety using
@dataclass(frozen=True, slots=True).
Time: ~45 minutes · Prerequisites: 02 — Data Model & Iterables
Reading Companion
- Book: Fluent Python (2nd Edition) — Luciano Ramalho
- Chapter 8: Type Hints in Functions.
- Chapter 15: More About Type Hints (Structural Typing with
Protocol, Variance, TypeVar).
Why this matters in production
In dynamic Python, passing a dictionary with a typo in key name (event["user_idd"]) causes a KeyError at runtime in production. With modern static typing (mypy, ruff, pyright), type errors are caught in your editor and CI before a single line runs. Furthermore, using Protocol allows you to decouple domain logic from database drivers and third-party libraries without rigid inheritance hierarchies.
Deep dive
1. Nominal vs Structural Subtyping (Protocol)
- Nominal Subtyping (
class Service(BaseService)): Explicit inheritance required. - Structural Subtyping (
Protocol): If a class implements the required methods and signatures, it satisfies the type—no inheritance needed!
from typing import Protocol
class LogSink(Protocol):
def emit(self, event: str) -> None: ...
Hands on
python3 phase0-foundations/03-python-internals/03-strict-typing-and-protocols/examples/01_demo.py
python3 phase0-foundations/03-python-internals/03-strict-typing-and-protocols/examples/02_trap.py
AI Trap & Code Review
# BAD AI-GENERATED CODE — OVERUSE OF Any IN INTERFACES
from typing import Any
def process_batch(items: list[Any]) -> list[Any]:
# Disables all type checking!
return [item.id for item in items]
Exercises
- Rung 1 (
make_event_record): Define a frozen slots dataclassEventRecord(id: str, payload: dict[str, str], timestamp: int)and instantiate it. - Rung 2 (
GenericStore— Fix the AI Bug): Implement a type-safeGenericStore[T]supporting.put(key: str, item: T)and.get(key: str) -> T | None. - Rung 3 (
EventSender Protocol): Define aProtocolnamedEventSenderwith methodsend(self, topic: str, message: str) -> bool. Verify structural adherence. - Rung 4 (
parse_and_validate_boundary): Validate an untrusted dict into an immutableUserAccountrecord, raisingValueErroron missing or incorrectly typed fields. - Rung 5 (
composite_pipeline): Build a typed pipeline function that composes generic transformationsT -> U -> V.
Wrapping up
Can you explain?
- What is the difference between nominal subtyping (
ABC) and structural subtyping (Protocol)? - Why does
@dataclass(slots=True)reduce memory consumption? - Why does type checking pass on
Anyeven when code crashes at runtime?
Further Reading & References
- CPython Internals & Data Model Reference
- PEP 544 — Protocols: Structural subtyping (static duck typing)
- PEP 557 — Data Classes
- Python Garbage Collector (gc module) Architecture
Show reference solution notes
Solution & Engineering Notes — 03 Strict Typing & Protocols
ProtocolvsABC:Protocolallows third-party libraries and callers to satisfy interfaces without importing or subclassing your base class.slots=True: Removes the instance's dynamic__dict__, storing attributes in a fixed-size C array, reducing memory per instance by ~60%.
Self-check
Run python3 -m unittest phase0-foundations/03-python-internals/03-strict-typing-and-protocols/test_exercises.py -v until green, then record it here. Saved to progress/rungs.json; commit it.
Completion edits PROGRESS.md; commit it with the lesson.