@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 (neverx: list = []).
Post-init validation
Define __post_init__ to run checks or compute derived fields right after the generated __init__ finishes.
Example
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 : FrozenInstanceErrorWhen 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.
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.99Frozen dataclass as hashable record
Makes the dataclass immutable and hashable with frozen=True so it can be stored in sets.
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.
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