Using default_factory in Python Dataclasses
In Python, the dataclasses module simplifies the
creation of classes by automatically generating boilerplate methods like
__init__ and __repr__. This article explains
the purpose of dataclasses.field with the
default_factory parameter, demonstrating how it prevents
shared mutable state bugs and allows you to generate dynamic default
values for each new class instance.
The Problem: Mutable Default Arguments
In standard Python functions and classes, using mutable objects (such as lists, dictionaries, or sets) as default values leads to unexpected behavior. Because default values are evaluated only once when the class or function is defined, every instance created without an explicit argument shares the exact same mutable object in memory.
To prevent this common bug, Python's @dataclass
decorator actively raises a ValueError if you attempt to
assign a mutable default directly:
from dataclasses import dataclass
@dataclass
class Team:
# This raises ValueError: mutable default <class 'list'> for field members is not allowed: use default_factory
members: list = []The Solution:
dataclasses.field(default_factory=...)
The primary purpose of dataclasses.field with
default_factory is to provide a zero-argument callable (a
function) that Python executes to produce a fresh default value every
time a new instance is instantiated.
Instead of assigning a shared object, you provide a callable such as
list, dict, or set:
from dataclasses import dataclass, field
@dataclass
class Team:
name: str
members: list = field(default_factory=list)
team_a = Team(name="Engineering")
team_b = Team(name="Marketing")
team_a.members.append("Alice")
print(team_a.members) # Output: ['Alice']
print(team_b.members) # Output: []Because list is passed to default_factory,
each Team instance receives its own unique list object.
Generating Dynamic Defaults
Beyond preventing mutable state issues, default_factory
is used to compute dynamic default values at the moment of object
creation.
For example, if you want a timestamp field to reflect the exact time
an instance is created rather than the time the class was imported, pass
a function like datetime.now to
default_factory:
from dataclasses import dataclass, field
from datetime import datetime
import uuid
@dataclass
class Session:
session_id: str = field(default_factory=lambda: str(uuid.uuid4()))
created_at: datetime = field(default_factory=datetime.now)Key Rules
- Pass the callable, do not call it: Write
default_factory=list, notdefault_factory=list(). - Zero arguments required: The callable passed to
default_factorymust take zero arguments. If arguments are needed, use alambdaor a custom helper function. - Mutual exclusivity: A field cannot define both a
standard
defaultand adefault_factory. You must choose one.