Immutable fields
Project overview and reading path
Several components share feature configuration. One component must not silently change the features seen by another.
Typical Python
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
features: list[str]
config = Config(["height", "width"])
config.features.append("area")
Freezing the record blocks field reassignment but permits mutation of the list. The append succeeds and changes shared state.
Alternative
@dataclass(frozen=True)
class Experiment:
seed: int
features: tuple[str, ...]
config = Experiment(seed=17, features=("height", "width"))
expanded_features = config.features + ("area",)
The tuple field has no .append, so that operation is rejected as
missing-attribute. Frozen fields reject reassignment, and Final protects
the default binding in checked code. The caller’s expanded_features is a new
tuple; config.features still contains only ("height", "width").
Immutability is shallow. A frozen record containing a mutable array still exposes that array’s mutation. Here each feature name is a string, so a tuple fits the actual contract. Environment parsing is a separate boundary.