Matheion

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) or Optional[str] from typing on older versions.
  • Containers: list[int], dict[str, float] (built-in generics from Python 3.9+); earlier code often used typing.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 raises FrozenInstanceError)—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 mypy or pyright once.
  • Build one frozen=True dataclass and try to mutate a field—read the error.
  • Explain why x: int = 0 must not appear before name: str if name has 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. 1.At runtime, does `def f(x: int) -> int:` force `x` to actually be an `int`?

  2. 2.Given `@dataclass(frozen=True)` on a class, what happens if you reassign a field on an instance?

  3. 3.In a dataclass, which field declaration order is legal?

  4. 4.You annotate `count: int` but call the function with the string `'5'`. What does Python do by default?

  5. 5.By default, what does `@dataclass` generate for you?