Use dataclass defaults safely with default_factory
Use dataclass defaults safely with default_factory
A mutable default must be created per instance, not shared accidentally by every instance of a class. BuildPlan has a list of targets and a dictionary of labels, so each field uses field(default_factory=...). The supplied factory is a zero-argument callable; it runs when an instance needs that default. Passing the built-in list and dict functions makes the intended empty containers explicit.
The example constructs two plans, then changes only first. The content assertions confirm that second remains empty, while the identity assertions confirm the two instances did not receive the same list or dictionary object. The printed result makes the concrete state visible: the first plan has one target, while the second plan has neither targets nor labels.
This pattern prevents shared defaults at construction time, but it does not make the containers thread-safe, validate their later contents, or stop callers from mutating them. Choose a factory that returns an appropriate new value each time. A factory can also construct a custom mutable object, but avoid factories with surprising side effects because normal dataclass construction will invoke them. Dataclasses and field(default_factory=...) are available in Python 3.7 and later.
The official dataclasses documentation specifies that default_factory is a zero-argument callable and documents its use for mutable defaults.
AI assistance disclosure: This article was drafted with AI assistance and should be reviewed for a project’s own construction and concurrency rules.
from dataclasses import dataclass, field
@dataclass
class BuildPlan:
targets: list[str] = field(default_factory=list)
labels: dict[str, str] = field(default_factory=dict)
first = BuildPlan()
second = BuildPlan()
first.targets.append("lint")
first.labels["owner"] = "api"
assert second.targets == []
assert second.labels == {}
assert first.targets is not second.targets
assert first.labels is not second.labels
print(first.targets, second.targets, second.labels)
['lint'] [] {}
Comments
Post a Comment