Prevent a mutable default list in a dataclass
Also published in our Blogger archive.
dataclasses.field(default_factory=list) calls list separately whenever an instance needs its default. That gives each TodoList an independent empty list. In the example, morning receives one item while evening remains empty; the identity assertion checks that the two attributes are not the same list object.
This matters for per-instance state such as pending tasks, warnings, or collected results. A shared mutable default would let one instance’s mutation become visible through another instance. Current dataclasses reject ordinary unhashable defaults as a guard against this common mistake, but default_factory is the direct, readable way to express the intended ownership.
The factory must be a zero-argument callable. list is appropriate here because it constructs an empty list each time; use another suitable factory when the desired initial value differs. This only separates defaults created by the dataclass. Passing the *same* list explicitly to two constructors would still intentionally share it. Dataclasses were added in Python 3.7. See the official field() documentation.
AI assistance disclosure: this article was drafted with AI assistance and should be adapted to the application’s own data rules.
from dataclasses import dataclass, field
@dataclass
class TodoList:
items: list[str] = field(default_factory=list)
morning = TodoList()
evening = TodoList()
morning.items.append("write tests")
assert morning.items == ["write tests"]
assert evening.items == []
assert morning.items is not evening.items
print(morning.items)
print(evening.items)
['write tests']
[]