Home/Learn/Python A–Z/Dataclasses — Boilerplate-Free Data Objects

Dataclasses — Boilerplate-Free Data Objects

Intermediate
OOP

@dataclass auto-generates __init__, __repr__ and __eq__ from type-annotated fields — with defaults, immutability (frozen=True), ordering, and field factories for mutables.

Overview

Half of OOP code is classes that just hold data — and writing __init__/__repr__/__eq__ for each is pure boilerplate. @dataclass (Python 3.7+) generates them from the field declarations. Add frozen=True for immutable, hashable value objects; order=True for sortable ones; field(default_factory=list) for safe mutable defaults (the mutable-default trap returns here, and dataclasses force you to handle it correctly). This is Java records / Lombok, built into the language.

From 15 Lines to 5

Annotate fields with types; defaults follow non-defaults. You get construction, comparison and display for free — and __post_init__ for validation.

@dataclass: init + repr + eq generated
from dataclasses import dataclass, field

@dataclass
class Student:
    name: str
    roll: int
    cgpa: float = 0.0
    skills: list[str] = field(default_factory=list)  # SAFE mutable default

    def __post_init__(self):              # validation hook
        if not 0 <= self.cgpa <= 10:
            raise ValueError("cgpa out of range")

s1 = Student("Asha", 41, 8.7, ["python"])
s2 = Student("Asha", 41, 8.7, ["python"])

print(s1)            # Student(name='Asha', roll=41, cgpa=8.7, skills=['python'])
print(s1 == s2)      # True — field-wise __eq__ generated

# skills=[] as a plain default would raise:
# ValueError: mutable default <class 'list'> ... use default_factory

frozen, order & asdict

frozen=True makes instances immutable AND hashable (usable in sets/dict keys). order=True generates < <= > >= from field order. asdict() converts to a plain dict — handy for JSON.

Immutable, sortable, serializable value objects
from dataclasses import dataclass, asdict

@dataclass(frozen=True, order=True)
class Version:
    major: int
    minor: int
    patch: int = 0

v1 = Version(1, 9)
v2 = Version(2, 0)

print(v1 < v2)             # True — compares (1,9,0) < (2,0,0)
print(sorted([v2, v1]))    # [Version(1,9,0), Version(2,0,0)]
releases = {v1, v2}        # hashable because frozen
# v1.major = 3             # FrozenInstanceError

print(asdict(v2))          # {'major': 2, 'minor': 0, 'patch': 0}

# When NOT to use dataclasses: behaviour-heavy classes with little data,
# or validation-heavy API models — that's Pydantic's job (later chapter).

Key Points to Remember

  • 1@dataclass generates __init__, __repr__, __eq__ from annotated fields
  • 2Mutable defaults MUST use field(default_factory=list) — enforced at class creation
  • 3frozen=True → immutable + hashable; order=True → sortable by field order
  • 4__post_init__ is the validation hook; asdict()/astuple() for serialization

Interview Questions

Sign in to ask Aria
1

What does @dataclass generate for you? How do defaults for mutable fields work?

MediumSwiggy
2

How do you make a dataclass usable as a dict key?

MediumMeesho
3

Dataclass vs namedtuple vs Pydantic model — when each?

HardRazorpay

Ask Aria about Dataclasses — Boilerplate-Free Data Objects

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…