Type annotations describe intent to readers and tools such as Pyright; Python does not automatically enforce them at runtime. Validate configuration read from JSON, environment variables or command-line text before using it.
Type annotations help tools, while the parser enforces the runtime boundary.
def twice(x: int) -> int:
return x * 2
print(twice(3))
print(twice("a")) # annotation did not prevent thisA dataclass supplies common record behavior such as initialization and value equality. A frozen dataclass prevents field reassignment; it does not deeply freeze a list stored in a field. Use immutable field values for configuration.
from dataclasses import dataclass
@dataclass(frozen=True)
class Run:
seed: int
labels: tuple[str, ...]
run = Run(7, ("control", "shift"))
print(run.seed, run.labels[1])At a boundary, be precise about Python's numeric hierarchy: bool is a subclass
of int. isinstance(True, int) succeeds. If a seed must be an actual integer,
reject bool explicitly or compare type(value) is int. Converting with int()
can silently truncate a float or accept text when strict validation was needed.
Python treats bool as an int subclass, so a strict integer seed needs a more precise test than isinstance.
The build converts a plain mapping to a frozen, fully validated RunConfig.
Unknown keys are rejected because a misspelled batch_size should not quietly
use a default. Defaults are part of the documented contract. Extra record
classes are unnecessary; prefer a small obvious data model over inheritance.