Skip to content

Fix: Python dataclass Mutable Default Value Error (ValueError / TypeError)

FixDevs ·

Quick Answer

How to fix Python dataclass mutable default errors — why lists, dicts, and sets cannot be default field values, how to use field(default_factory=...), and common dataclass pitfalls with inheritance and ClassVar.

The Error

Defining a dataclass with a mutable default value raises:

from dataclasses import dataclass

@dataclass
class Config:
    tags: list = []  # ← Error here

# ValueError: mutable default <class 'list'> for field tags is not allowed:
# use default_factory

Or with a dict:

@dataclass
class Request:
    headers: dict = {}

# ValueError: mutable default <class 'dict'> for field headers is not allowed:
# use default_factory

Or, in Python 3.11+ with certain patterns:

TypeError: unhashable type: 'list'

Why This Happens

Python dataclasses prohibit mutable objects (lists, dicts, sets, custom objects) as direct default values. The reason is the same problem as Python’s infamous mutable default argument bug:

# The problem without dataclasses — same object shared across all instances
class Config:
    def __init__(self, tags=[]):  # Same list object for every instance!
        self.tags = tags

a = Config()
b = Config()
a.tags.append('python')
print(b.tags)  # ['python'] — b was unexpectedly modified!

Dataclasses detect this problem at class definition time and raise an error rather than silently sharing state. The fix is field(default_factory=...), which creates a new object for each instance.

Fix 1: Use field(default_factory=…) for Mutable Defaults

from dataclasses import dataclass, field

# Before — raises ValueError
@dataclass
class Config:
    tags: list = []
    metadata: dict = {}
    aliases: set = set()

# After — correct
@dataclass
class Config:
    tags: list = field(default_factory=list)        # Creates [] for each instance
    metadata: dict = field(default_factory=dict)    # Creates {} for each instance
    aliases: set = field(default_factory=set)       # Creates set() for each instance

With type hints (recommended):

from dataclasses import dataclass, field
from typing import Any

@dataclass
class ServerConfig:
    host: str = 'localhost'
    port: int = 8080
    allowed_hosts: list[str] = field(default_factory=list)
    headers: dict[str, str] = field(default_factory=dict)
    options: dict[str, Any] = field(default_factory=dict)

# Each instance gets its own fresh list/dict
config1 = ServerConfig()
config2 = ServerConfig()
config1.allowed_hosts.append('example.com')
print(config2.allowed_hosts)  # [] — not affected

Pre-populate with default values using a lambda:

@dataclass
class APIClient:
    # Create a new list with default values for each instance
    base_headers: dict[str, str] = field(
        default_factory=lambda: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        }
    )
    retry_codes: list[int] = field(default_factory=lambda: [429, 500, 502, 503])
    timeout: float = 30.0

Pro Tip: Use field(default_factory=lambda: [...]) when you want a mutable default that is pre-populated. Use field(default_factory=list) when you just want an empty list. The lambda creates a new copy of the default on every instantiation.

Fix 2: Fix Nested Dataclass Defaults

When a field’s default value is another dataclass instance (which is mutable):

@dataclass
class DatabaseConfig:
    host: str = 'localhost'
    port: int = 5432

@dataclass
class AppConfig:
    # Wrong — same DatabaseConfig instance shared across all AppConfig instances
    db: DatabaseConfig = DatabaseConfig()  # ValueError in Python 3.11+
                                           # Silently shared in earlier versions!

    # Correct — create a new DatabaseConfig for each AppConfig
    db: DatabaseConfig = field(default_factory=DatabaseConfig)

For nested dataclasses with custom defaults:

@dataclass
class AppConfig:
    db: DatabaseConfig = field(
        default_factory=lambda: DatabaseConfig(host='db.internal', port=5432)
    )

Fix 3: Fix ClassVar vs Instance Variables

If you want a class-level attribute (shared across all instances), use ClassVar — it is excluded from __init__ and not subject to the mutable default restriction:

from dataclasses import dataclass
from typing import ClassVar

@dataclass
class Registry:
    name: str
    value: int = 0

    # ClassVar — shared across all instances (not in __init__)
    # Mutable ClassVar is allowed — it's intentionally shared
    _instances: ClassVar[list['Registry']] = []
    _registry: ClassVar[dict[str, 'Registry']] = {}

    def __post_init__(self):
        Registry._instances.append(self)
        Registry._registry[self.name] = self

r1 = Registry('alpha', 1)
r2 = Registry('beta', 2)
print(Registry._instances)  # [Registry(name='alpha'), Registry(name='beta')]

Fix 4: Use post_init for Complex Initialization

When the default value depends on other fields or requires complex logic:

from dataclasses import dataclass, field
from pathlib import Path

@dataclass
class Project:
    name: str
    base_dir: Path = Path('.')

    # These cannot be set directly as defaults because they depend on other fields
    source_dir: Path = field(init=False)
    output_dir: Path = field(init=False)
    tags: list[str] = field(default_factory=list)

    def __post_init__(self):
        # Compute dependent fields after __init__ runs
        self.source_dir = self.base_dir / 'src'
        self.output_dir = self.base_dir / 'dist'

        # Validate fields
        if not self.name:
            raise ValueError('Project name cannot be empty')

        # Normalize types
        if isinstance(self.base_dir, str):
            self.base_dir = Path(self.base_dir)

project = Project(name='my-app', base_dir=Path('/projects/my-app'))
print(project.source_dir)  # /projects/my-app/src

Fix 5: Fix Dataclass Inheritance Issues

Dataclass inheritance has a specific limitation — subclasses cannot define fields without defaults if the parent class has fields with defaults:

@dataclass
class Base:
    name: str = 'default'   # Field with default

@dataclass
class Child(Base):
    age: int  # ← TypeError: non-default argument 'age' follows default argument

Fix — give the child field a default too, or restructure:

# Option A — give child field a default
@dataclass
class Child(Base):
    age: int = 0  # Now has a default

# Option B — put required fields in the parent
@dataclass
class Base:
    name: str  # Required — no default

@dataclass
class Child(Base):
    age: int   # Also required — no default
    extra: list = field(default_factory=list)  # Optional with default

child = Child(name='Alice', age=30)

# Option C — use field(kw_only=True) in Python 3.10+
@dataclass
class Base:
    name: str = 'default'

@dataclass
class Child(Base):
    age: int = field(kw_only=True)  # Keyword-only — avoids ordering conflict

child = Child(age=30)  # name uses default

Fix 6: Fix Frozen Dataclasses with Mutable Fields

Frozen dataclasses (frozen=True) cannot be modified after creation — but they can still contain mutable objects:

from dataclasses import dataclass, field

@dataclass(frozen=True)
class ImmutableConfig:
    name: str
    values: tuple = ()       # Use tuple (immutable) instead of list
    options: frozenset = field(default_factory=frozenset)  # frozenset instead of set

# Attempting to modify raises FrozenInstanceError
config = ImmutableConfig(name='test')
config.name = 'changed'     # FrozenInstanceError: cannot assign to field 'name'

# But the contained list IS still mutable (if you used list):
@dataclass(frozen=True)
class BadFrozen:
    items: list = field(default_factory=list)

bad = BadFrozen()
bad.items.append(1)  # No error — list itself is mutable even if the reference is frozen
# Use tuple for truly immutable sequences in frozen dataclasses

Fix 7: Convert to/from Dict and JSON

from dataclasses import dataclass, field, asdict, astuple
import json

@dataclass
class User:
    id: int
    name: str
    email: str
    roles: list[str] = field(default_factory=list)
    metadata: dict = field(default_factory=dict)

user = User(id=1, name='Alice', email='[email protected]', roles=['admin'])

# Convert to dict
user_dict = asdict(user)
print(user_dict)
# {'id': 1, 'name': 'Alice', 'email': '[email protected]', 'roles': ['admin'], 'metadata': {}}

# Convert to JSON
user_json = json.dumps(asdict(user))

# Create from dict
data = {'id': 2, 'name': 'Bob', 'email': '[email protected]'}
user2 = User(**data)  # Works — roles and metadata use defaults

For nested dataclasses, asdict recursively converts:

@dataclass
class Address:
    city: str
    country: str = 'US'

@dataclass
class Person:
    name: str
    address: Address

person = Person(name='Alice', address=Address(city='New York'))
print(asdict(person))
# {'name': 'Alice', 'address': {'city': 'New York', 'country': 'US'}}

Still Not Working?

Check Python version. Some dataclass features (kw_only, slots) require Python 3.10+. The match statement for dataclasses requires Python 3.10+:

python --version

Use dataclasses.fields() to inspect a dataclass at runtime:

from dataclasses import dataclass, field, fields

@dataclass
class Config:
    name: str
    values: list = field(default_factory=list)

for f in fields(Config):
    print(f.name, f.type, f.default, f.default_factory)

Consider attrs or Pydantic for more complex validation needs:

# Pydantic — dataclass alternative with built-in validation
pip install pydantic

from pydantic.dataclasses import dataclass  # Drop-in replacement with validation

@dataclass
class User:
    name: str
    age: int
    tags: list[str] = []  # Pydantic handles mutable defaults automatically

user = User(name='Alice', age=30)  # Works — no ValueError

For related Python issues, see Fix: Python TypeError Missing Required Argument and Fix: Python AttributeError NoneType Has No Attribute.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles