MA-PY1 · Module 4.4
Type hints and dataclasses
Topics: typing · Protocols (awareness) · dataclass · frozen
Overview
Module 4.4 — Type hints and dataclasses
Scroll Core ideas → worked snippets → Practice (quiz). Attempt each question before Discuss / reveal. Use any Python REPL, script, or notebook you like.
Concepts
Core ideas — annotations & data-focused classes
Python is dynamically typed at runtime, yet type hints (PEP 484+) let you document contracts and run static analysers (mypy, pyright, IDE engines) before shipping.
Syntax essentials
def add(a: int, b: int) -> int:
return a + b
- Parameters use
name: Type; the return uses-> Type. - Optional values:
name: str | None(Python 3.10+ union) orOptional[str]fromtypingon older versions. - Containers:
list[int],dict[str, float](built-in generics from Python 3.9+); earlier code often usedtyping.List,typing.Dict.
Types are hints by default—no automatic runtime checks unless you add a checker library.
typing toolbox (minimal set)
| Symbol | Meaning |
|--------|---------|
| Callable[[int], bool] | Function taking int, returning bool. |
| Iterable[str] | Anything you can iterate for str items. |
| Sequence[float] | Sized, indexable “list-like” (e.g. list, tuple). |
| Protocol | Structural typing (“has these methods”)—use after you’re comfortable with classes. |
@dataclass motivation
Plain data objects often need __init__, __repr__, and equality—writing that by hand is repetitive and easy to get out of sync.
@dataclass (from dataclasses) generates boilerplate for field declarations.
By default you get __init__ and __repr__; eq=True (default) adds __eq__.
Useful options:
frozen=True: instances become immutable (assigning a field raisesFrozenInstanceError)—handy for hashable records if all fields are hashable.slots=True: optional memory optimisation (Python 3.10+ on@dataclass) — skip until you profile.- Field order: fields without defaults must come before fields with defaults (same rule as a normal
__init__).
typing.NamedTuple is a lightweight alternative for small immutable bundles.
When a hand-written class is better
Heavy validation, async setup, or tricky inheritance may be clearer without piling logic into __post_init__. Use @dataclass when the object is mostly data with light behaviour.
Self-study checklist
- Add return types to a few functions and run
mypyorpyrightonce. - Build one
frozen=Truedataclass and try to mutate a field—read the error. - Explain why
x: int = 0must not appear beforename: strifnamehas no default.
Examples
Worked snippets — hints & dataclasses
Annotated function
def clamp(x: float, lo: float, hi: float) -> float:
return max(lo, min(hi, x))
print(clamp(1.5, 0.0, 1.0))
Union / optional
def greet(name: str | None) -> str:
if name is None:
return "Hello, stranger"
return f"Hello, {name}"
print(greet(None), greet("Ada"))
Basic dataclass
from dataclasses import dataclass
@dataclass
class Reading:
sensor_id: str
value: float
unit: str = "C"
r = Reading("T1", 23.5)
print(r)
Frozen (immutable) record
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(2, 3)
# p.x = 0 # FrozenInstanceError
__post_init__ validation
from dataclasses import dataclass
@dataclass
class Grade:
score: float
def __post_init__(self):
if not 0 <= self.score <= 100:
raise ValueError("score out of range")
print(Grade(88.0))
Collection hints
def total(xs: list[int]) -> int:
return sum(xs)
print(total([1, 2, 3]))
For read-only “any iterable of ints”, prefer Iterable[int] from typing when you formalise APIs.
Practice check — type hints and dataclasses
Multiple-choice check from the Matheion app lesson. Pick an answer for each question, then submit.
1.At runtime, does `def f(x: int) -> int:` force `x` to actually be an `int`?
2.Given `@dataclass(frozen=True)` on a class, what happens if you reassign a field on an instance?
3.In a dataclass, which field declaration order is legal?
4.You annotate `count: int` but call the function with the string `'5'`. What does Python do by default?
5.By default, what does `@dataclass` generate for you?