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.
Search across all documentation pages
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.
from dataclasses import dataclass, field
@dataclass
class User:
id: int
name: str
tags: list[str] = field(default_factory=list)When to reach for this:
frozen=True)__repr__ for debuggingfrom 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=True prevents attribute reassignment after creation__post_init__ validates invariants post-__init__default_factory=list creates fresh list per instancefield(kw_only=True) forces keyword passing for late fields (3.10+).@dataclass(slots=True) combines dataclass with __slots__ (3.10+).dataclasses.replace(obj, amount=200) functional updates on frozen instances.| 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+) |
from dataclasses import asdict, astuple
payload = asdict(order) # shallow dict - nested dataclasses recurse
coords = astuple(point)tags: list = [] still broken without field. Fix: field(default_factory=list).lines but list inside still mutable. Fix: Tuple fields or immutable types.| 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 |
dataclass flexible mutability and methods. NamedTuple lighter immutable tuple subclass.
__post_init__ or external Pydantic layer at boundaries. dataclasses do not auto-validate types at runtime.
Yes but often unnecessary - use __post_init__ instead to keep generated init.
Frozen dataclasses hashable when all fields hashable - great for dict keys.
default for immutable defaults (int, str, None). default_factory for new mutable each time.
Millions of instances - saves memory. Small counts - skip complexity.
Supported - watch field order and defaults across parent/child.
Handy for simple serialization - watch datetime/decimal custom types need custom encoder.
Forces keyword for that field when creating instance - clearer APIs with many params.
Stay stdlib unless attrs features (validators, converters) already standard in your org.
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+.
Reviewed by Chris St. John·Last updated Jul 16, 2026