Dataclasses: field(default_factory=...), __post_init__, frozen=True, generated __eq__
@dataclass auto-generates __init__, __repr__, and __eq__ from the declared fields. The mutable
default argument trap (topic 02) still applies to dataclass fields -- field(default_factory=list) is the
sanctioned fix, since a bare items: list = [] is a TypeError at class-definition time (dataclasses
detect and reject it outright, rather than letting the shared-mutable-default bug happen silently).
from dataclasses import dataclass, field
@dataclass
class Bucket:
items: list = field(default_factory=list)
b1 = Bucket()
b2 = Bucket()
b1.items.append(1)
print("independent lists, not shared:", b1.items, b2.items)
__post_init__ runs right after the generated __init__ -- the idiomatic place for computed/derived fields:
@dataclass
class Rect:
w: float
h: float
area: float = field(init=False) # not a constructor parameter
def __post_init__(self):
self.area = self.w * self.h
r = Rect(3, 4)
print("computed field:", r.area)
frozen=True makes instances immutable after construction (raises on any attribute assignment), and the generated __eq__ compares field values -- but ordering (<, >) is NOT generated unless you ask for it:
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
try:
p.x = 99
except Exception as e:
print(f"frozen dataclass assignment: {type(e).__name__}: {e}")
print("generated __eq__ compares values:", Point(1, 2) == Point(1, 2))
try:
Point(1, 2) < Point(3, 4)
except TypeError as e:
print("no ordering without order=True:", e)