devnotes.

Dataclasses

The @dataclass decorator (Python 3.7+) automatically generates the boilerplate — __init__, __repr__, __eq__, and more — for classes whose main purpose is holding data.

from dataclasses import dataclass, field

@dataclass
class Point:
    x: int
    y: int
    label: str = "origin"          # default value, just like a regular parameter

p1 = Point(3, 4)
p2 = Point(3, 4)
print(p1)               # Point(x=3, y=4, label='origin') — __repr__ generated automatically
print(p1 == p2)          # True — __eq__ generated automatically, compares field values

@dataclass(frozen=True)   # makes instances immutable, like a namedtuple
class ImmutablePoint:
    x: int
    y: int

@dataclass
class Team:
    name: str
    members: list[str] = field(default_factory=list)  # correct way to default to a mutable type

Without @dataclass, writing an equivalent plain class means manually writing __init__ (assigning every field to self), __repr__ (for readable printing), and __eq__ (for value-based comparison) — @dataclass generates all three automatically purely from the class's type-annotated fields, dramatically reducing boilerplate for simple data-holding classes.

@dataclass(frozen=True) makes instances immutable after creation (attempting to reassign a field raises an error), similar in spirit to a namedtuple but with full type hints and the ability to add regular methods alongside the fields.

For mutable default values like a list, use field(default_factory=list) rather than members: list = [] directly — this avoids exactly the same shared-mutable-default trap covered in the earlier lesson on function default arguments, since @dataclass handles creating a fresh list per instance when a factory function is supplied.

Python Dataclasses Explained | DevNotes