dataclasses
dataclasses (stdlib since 3.7) generates common method boilerplate for classes that primarily hold data. They are the default choice for internal records before reaching for full ORM models.
Recipe
from dataclasses import dataclass, field
@dataclass
class User:
id: int
name: str
tags: list[str] = field(default_factory=list)When to reach for this:
- Domain events and DTOs inside application layers
- Immutable config/value objects (
frozen=True) - Nested structures with readable
__repr__for debugging - Replacing tuples where field names help readability
Working Example
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Money:
amount: int
currency: str
def __post_init__(self) -> None:
if self.amount < 0:
raise ValueError("amount must be non-negative")
@dataclass
class Order:
id: int
lines: list[Money] = field(default_factory=list)
def total(self) -> Money:
if not self.lines:
return Money(0, "USD")
currency = self.lines[0].currency
amount = sum(line.amount for line in self.lines)
return Money(amount, currency)
if __name__ == "__main__":
order = Order(1, [Money(100, "USD"), Money(50, "USD")])
print(order)
print(order.total())What this demonstrates:
frozen=Trueprevents attribute reassignment after creation__post_init__validates invariants post-__init__default_factory=listcreates fresh list per instance- Methods coexist with generated dataclass methods
Deep Dive
How It Works
- Decorator transforms class - Adds methods based on fields and flags at class creation time.
- Field ordering - Fields without defaults must precede fields with defaults.
- KW_ONLY -
field(kw_only=True)forces keyword passing for late fields (3.10+). - slots -
@dataclass(slots=True)combines dataclass with__slots__(3.10+). - replace -
dataclasses.replace(obj, amount=200)functional updates on frozen instances.
Useful Flags
| Flag | Effect |
|---|---|
frozen=True | Immutable + hashable (if fields hashable) |
order=True | Generate ordering comparisons |
slots=True | Memory-efficient instances |
kw_only=True | All fields keyword-only (3.10+) |
Python Notes
from dataclasses import asdict, astuple
payload = asdict(order) # shallow dict - nested dataclasses recurse
coords = astuple(point)Gotchas
- Mutable default list -
tags: list = []still broken without field. Fix:field(default_factory=list). - Inheritance field ordering - Subclass defaults tricky across MRO. Fix: Keep hierarchies shallow or use composition.
- asdict deep mutables - Copies structure but nested mutables still alias if not dataclasses. Fix: Know shallow vs deep needs.
- frozen with mutable fields - Frozen prevents rebinding
linesbut list inside still mutable. Fix: Tuple fields or immutable types. - Comparing to dict - Dataclass instance != dict even if fields match. Fix: Convert explicitly for JSON APIs.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
NamedTuple | Immutable tuple-like | Need mutability |
TypedDict | JSON dict shape | Need methods/validation |
Pydantic BaseModel | HTTP validation | Inner domain without I/O |
| plain class | Complex lifecycle | Simple record boilerplate |
FAQs
dataclass vs NamedTuple?
dataclass flexible mutability and methods. NamedTuple lighter immutable tuple subclass.
How validate fields?
__post_init__ or external Pydantic layer at boundaries. dataclasses do not auto-validate types at runtime.
Can I customize __init__?
Yes but often unnecessary - use __post_init__ instead to keep generated init.
frozen and hashable?
Frozen dataclasses hashable when all fields hashable - great for dict keys.
field(default=...) vs default_factory?
default for immutable defaults (int, str, None). default_factory for new mutable each time.
slots=True worth it?
Millions of instances - saves memory. Small counts - skip complexity.
inheritance with dataclasses?
Supported - watch field order and defaults across parent/child.
asdict for JSON?
Handy for simple serialization - watch datetime/decimal custom types need custom encoder.
kw_only on one field?
Forces keyword for that field when creating instance - clearer APIs with many params.
dataclass vs attrs?
Stay stdlib unless attrs features (validators, converters) already standard in your org.
Related
- Dunder / Magic Methods - generated methods
- Properties & Descriptors - computed fields
- TypedDict & NamedTuple - typing alternatives
- Immutability & Hashability - frozen objects
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.