Python Dataclass Default Factory: Safe Mutable Defaults
python dataclass default factory: Learn how to use default_factory in Python dataclasses to safely handle mutable default values like lists and dicts, with practical e...
python dataclass default factory requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you declare a dataclass field with a mutable default, Python raises a ValueError at class definition time. This is intentional: a shared mutable object would be reused across all instances, causing subtle bugs. The field(default_factory=...) mechanism exists to solve this. Instead of providing a fixed value, you provide a zero-argument callable that creates a fresh object each time an instance is constructed.
The Problem with Mutable Defaults
Consider a naive attempt to give every Order instance its own empty list of items:
from dataclasses import dataclass @dataclass class Order: items: list = [] # ValueError: mutable default <class 'list'> for field items is not allowed
This fails immediately. Even if it were allowed, all Order instances would share the same list. Appending to one order would affect every other order. The default_factory parameter avoids this by calling a factory function for each new instance.
Using default_factory Correctly
The field function from the dataclasses module accepts a default_factory argument. This callable is invoked without arguments whenever the dataclass constructor runs and no explicit value is supplied for that field.
from dataclasses import dataclass, field @dataclass class Order: items: list = field(default_factory=list) metadata: dict = field(default_factory=dict)
Now each Order gets its own independent list and dict. The built-in list, dict, and set types are ideal factories because they are callable and return empty collections.
When to Use default_factory vs a Default Value
For immutable types such as int, str, float, bool, and tuple, a plain default value is fine. The value is shared, but since it cannot be mutated, sharing is harmless. For any mutable type—list, dict, set, or a custom mutable class—use default_factory.
| Field type | Recommended default | Reason |
|---|---|---|
int | 0 | Immutable, safe to share |
str | "" | Immutable, safe to share |
list | field(default_factory=list) | Mutable, needs per-instance copy |
dict | field(default_factory=dict) | Mutable, needs per-instance copy |
set | field(default_factory=set) | Mutable, needs per-instance copy |
tuple | () | Immutable, safe to share |
Using default_factory with Custom Objects
The factory can be any callable that returns an object of the expected type. This includes classes, functions, and lambdas. For example, a Profile class that requires no arguments can be used directly:
@dataclass class Profile: username: str bio: str = "" @dataclass class User: name: str profile: Profile = field(default_factory=Profile)
If the custom object requires arguments, you can use a lambda or a named function:
@dataclass class User: name: str profile: Profile = field(default_factory=lambda: Profile(username="guest"))
Be careful with lambdas: they must be simple and readable. A named factory function is often clearer when the construction logic is nontrivial.
Common Mistakes and How to Avoid Them
One frequent error is passing a function call instead of the function itself. field(default_factory=list()) would call list() immediately, producing an empty list as the default value, which triggers the same ValueError as a mutable default. The factory must be a callable, not the result of a call.
Another mistake is using default_factory for immutable fields. It works, but it adds unnecessary overhead and obscures intent. Prefer plain defaults for immutable types.
A subtle issue arises when the factory function is defined inside a loop or has a closure over a mutable variable. For example:
items = [1, 2, 3] @dataclass class Example: data: list = field(default_factory=lambda: items)
This returns the same items list for every instance, defeating the purpose. The factory must create a new object, not return a reference to an existing one.
Runtime Behavior and Performance
The default_factory is called every time the dataclass constructor runs and no explicit value is provided for that field. This means each new instance gets a fresh object. The cost is the overhead of one function call and the construction of the object. For built-in types like list() and dict(), this is negligible. For expensive custom objects, consider whether the default is needed often or whether a shared immutable alternative exists.
Because the factory is invoked per instance, it also runs during deserialization or any process that constructs new dataclass instances. This is usually desirable, but be aware that the factory should not have side effects beyond creating the default value. If the factory reads from a global configuration or a database, it will execute on every construction, which can be surprising and slow.
Default Factory with post_init for Dependent Defaults
default_factory cannot reference other fields because it receives no arguments. When a default value depends on another field's value, use __post_init__ instead. For example, a ShoppingCart might need a discount_code that defaults based on the customer_tier:
@dataclass class Cart: customer_tier: str discount_code: str = "" def __post_init__(self): if not self.discount_code and self.customer_tier == "gold": self.discount_code = "GOLD10"
This keeps the logic explicit and avoids the need for a factory that would have to inspect the instance.
Maintainability and Production Considerations
Using default_factory consistently makes dataclass definitions predictable. New developers on a team can immediately see which fields are mutable and which are not. It also prevents a class of bugs that are difficult to trace, such as one instance mutating another's list.
When serializing dataclasses to JSON or other formats, default_factory fields behave like normal fields. The serialized output includes the default-created objects, which is usually what you want. If you need to distinguish between a field that was explicitly set to an empty list and one that was never provided, you may need to use field(default=None) and handle None in __post_init__. That is a separate design decision.
Finally, keep the factory callable simple. If the default value requires complex construction, extract it into a dedicated function or class method. This improves testability and keeps the dataclass definition readable.