backend / python core / tricky questions / 18_dataclass_mutable_default.md

Dataclass mutable defaults

1 min read source

Dataclass mutable defaults

The gotcha

Same root issue as 01_mutable_default_arguments.md, but specific to @dataclass: trying to use field: list = [] raises a ValueError at class construction time. You must use field(default_factory=list).

Minimal repro

from dataclasses import dataclass

@dataclass
class Bad:
    items: list = []
# ValueError: mutable default <class 'list'> for field items is not allowed:
# use default_factory

The error message is helpful — Python catches the bug at class-definition time for dataclasses. Without dataclass:

class Bad:
    items = []   # silent bug — class variable shared by all instances

a = Bad(); b = Bad()
a.items.append(1)
b.items   # [1]   ← shared

Why it happens

@dataclass validates field defaults. Mutable types (list, dict, set) raise immediately because the framework knows you almost certainly meant “fresh value per instance”. Non-mutable defaults (int, str, None, frozen dataclasses) are fine.

default_factory is a callable invoked once per __init__ call:

from dataclasses import dataclass, field

@dataclass
class Good:
    items: list = field(default_factory=list)
    metadata: dict = field(default_factory=dict)
    tags: set = field(default_factory=set)

Subtler variants

Frozen dataclass with mutable nested values

@dataclass(frozen=True)
class Config:
    options: list = field(default_factory=list)

c = Config()
c.options.append("x")   # works — frozen blocks attribute *rebinding*, not mutation
c.options = []          # FrozenInstanceError

frozen=True prevents setting attributes, but doesn’t make their values immutable. Use tuple or frozenset if you want true immutability.

Sentinel default

If “no value” is meaningful and None is a valid value, use dataclasses.MISSING sentinel or a custom one:

_UNSET = object()

@dataclass
class Settings:
    timeout: object = field(default=_UNSET)

Interview angle

“What’s wrong with @dataclass\nclass C:\n items: list = []?” Then: “How would you fix it?” — field(default_factory=list) is the answer.