@dataclass

Auto-generate __init__, __repr__ and __eq__ from a few typed fields — less boilerplate, fewer bugs.

Writing a class that just holds data means typing __init__, __repr__ and __eq__ by hand — tedious and easy to get subtly wrong. The @dataclass decorator generates all of them from your field annotations.

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int = 0        # default value

p = Point(3, 4)
print(p)              # Point(x=3, y=4)   <- free __repr__
print(p == Point(3, 4))   # True          <- free __eq__

The options you will actually use

  • @dataclass(frozen=True) — makes instances immutable and hashable, so they work as dict keys and set members.
  • @dataclass(order=True) — generates < <= > >= so instances sort.
  • field(default_factory=list) — the correct way to give a field a fresh mutable default (never x: list = []).

Post-init validation

Define __post_init__ to run checks or compute derived fields right after the generated __init__ finishes.

Example

Example · python
from dataclasses import dataclass, field

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

    def __post_init__(self):
        if self.major < 0:
            raise ValueError('major must be non-negative')

    def bump_minor(self):
        # frozen: build a NEW object instead of mutating
        return Version(self.major, self.minor + 1, 0)

v1 = Version(1, 4, 2)
v2 = Version(1, 5)
print('v1        :', v1)                 # free __repr__
print('v1 < v2   :', v1 < v2)            # True, from order=True
print('bumped    :', v1.bump_minor())    # Version(major=1, minor=5, patch=0)
print('hashable  :', {v1, v2, Version(1, 4, 2)})   # dedupes v1
print('sorted    :', sorted([v2, v1]))

try:
    v1.major = 9                          # frozen -> blocked
except Exception as e:
    print('immutable :', type(e).__name__)

# Output:
# v1        : Version(major=1, minor=4, patch=2, tags=())
# v1 < v2   : True
# bumped    : Version(major=1, minor=5, patch=0, tags=())
# hashable  : {Version(major=1, minor=4, patch=2, tags=()), Version(major=1, minor=5, patch=0, tags=())}
# sorted    : [Version(major=1, minor=4, patch=2, tags=()), Version(major=1, minor=5, patch=0, tags=())]
# immutable : FrozenInstanceError

When to use it

  • A config loader maps a JSON response to a typed dataclass, eliminating a hand-written __init__.
  • An event system uses a frozen dataclass as an immutable message object that can be hashed and stored in sets.
  • A REST serialiser uses dataclasses.asdict() to convert a Product dataclass to a JSON-ready dictionary.

More examples

Basic dataclass

Defines a Product with auto-generated __init__, __repr__, and __eq__ from annotated fields.

Example · python
from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float
    in_stock: bool = True

p = Product('Laptop', 999.99)
print(p)          # Product(name='Laptop', price=999.99, in_stock=True)
print(p.price)    # 999.99

Frozen dataclass as hashable record

Makes the dataclass immutable and hashable with frozen=True so it can be stored in sets.

Example · python
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: float
    y: float

p = Point(1.0, 2.0)
print(hash(p))      # hashable because it's frozen
seen = {p, Point(1.0, 2.0)}
print(len(seen))    # 1 (deduped)

dataclasses.asdict and field defaults

Uses field(default_factory=list) to avoid sharing a mutable default and asdict() to serialise.

Example · python
from dataclasses import dataclass, field, asdict
from typing import List

@dataclass
class Order:
    order_id: int
    items: List[str] = field(default_factory=list)

o = Order(42)
o.items.append('Widget')
print(asdict(o))   # {'order_id': 42, 'items': ['Widget']}

Discussion

  • Be the first to comment on this lesson.