💡 Python QuickBits - 🛡️ Safer Defaults with default_factory


Description:

When working with dataclasses in Python, a common pitfall is using mutable objects like [] or {} as field defaults. In older Python versions, this caused all instances of a class to share the same list or dict.

From Python 3.11 onwards, this isn’t even allowed — you’ll get a ValueError telling you to use default_factory instead.


The Wrong Way: Mutable Defaults

from dataclasses import dataclass
from typing import List

@dataclass
class BadTagStore:
    tags: List[str] = []   # ❌ BAD: raises ValueError in Python 3.11+

⛔ In Python 3.11+, this immediately raises:

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

In older versions (<3.11), this silently worked but caused shared mutable state across instances.


The Fix: default_factory

The correct way is to use field(default_factory=...), which creates a fresh object for every instance:

from dataclasses import dataclass, field
from typing import List

@dataclass
class GoodTagStore:
    tags: List[str] = field(default_factory=list)  # ✅ GOOD

a = GoodTagStore()
b = GoodTagStore()
a.tags.append("python")

print("a:", a.tags)   # ['python']
print("b:", b.tags)   # [] — independent

Practical Variations

default_factory works not just with lists, but also with dicts, sets, and even custom functions:

from dataclasses import dataclass, field
from typing import Dict, List

@dataclass
class ConfigStore:
    config: Dict[str, str] = field(default_factory=dict)  
    features: set = field(default_factory=set)           
    defaults: List[str] = field(default_factory=lambda: ["default"])

Every instance of ConfigStore gets its own independent defaults.


Key Points

  • Don’t use [] or {} as dataclass defaults.
  • Use field(default_factory=...).
  • Python 3.11+ enforces this best practice with a ValueError.
  • Works for any mutable type: list, dict, set, or custom factories.

Code Snippet:

from dataclasses import dataclass, field   # dataclass utilities and safe default factory
from typing import List, Dict              # type hints for clarity


from dataclasses import dataclass
from typing import List

try:
    @dataclass
    class BadTagStore:
        tags: List[str] = []   # ❌ BAD: raises ValueError in Python 3.11+

except ValueError as ve:
    print(f"ValueError - {str(ve)}")
# Running this in Python 3.11+ will throw:
# ValueError: mutable default  for field tags is not allowed: use default_factory


@dataclass                                       # safer dataclass
class GoodTagStore:                              # corrected class
    tags: List[str] = field(default_factory=list)  # ✅ GOOD: a new list each time

good1 = GoodTagStore()                           # first safe instance
good2 = GoodTagStore()                           # second safe instance
good1.tags.append("python")                      # mutate tags on first instance

print("Good store1:", good1.tags)                # ['python']
print("Good store2:", good2.tags)                # [] (independent)
print("Same object?", good1.tags is good2.tags)  # False


@dataclass
class ConfigStore:
    # dict gets a fresh copy per instance
    config: Dict[str, str] = field(default_factory=dict)

    # set gets a fresh copy per instance
    features: set = field(default_factory=set)

    # even a custom factory function
    def_list: List[str] = field(default_factory=lambda: ["default"])

c1 = ConfigStore()
c2 = ConfigStore()
c1.config["mode"] = "dark"

print("Config1:", c1.config)        # {'mode': 'dark'}
print("Config2:", c2.config)        # {} — independent

print("Features1:", c1.features)    # set()
print("Default list2:", c2.def_list) # ['default']

Link copied!

Comments

Add Your Comment

Comment Added!