Python Dataclasses Cheat Sheet
The @dataclass decorator, field options, frozen/slots variants, and post-init hooks for writing boilerplate-free data-holding classes.
Basic Usage
@dataclass auto-generates __init__, __repr__, and __eq__ from type-annotated fields.
from dataclasses import dataclass@dataclassclass Point: x: float y: float = 0.0 # default valuep = Point(3.0, 4.0)print(p) # Point(x=3.0, y=4.0)print(p == Point(3.0, 4.0)) # True, __eq__ compares fields
field() Options
Use field() for defaults that need factories, exclusion from repr, or metadata.
from dataclasses import dataclass, field@dataclassclass Team: name: str members: list[str] = field(default_factory=list) # never use mutable defaults directly secret_key: str = field(default="", repr=False) # hidden from __repr__ id: int = field(compare=False, default=0) # excluded from __eq__/__lt__
frozen, slots & order
Common decorator parameters that change generated behavior.
@dataclass(frozen=True) # immutable: raises on attribute assignmentclass ImmutablePoint: x: float y: float@dataclass(slots=True) # adds __slots__, saves memory, no __dict__class FastPoint: x: float y: float@dataclass(order=True) # generates __lt__, __le__, __gt__, __ge__class Version: major: int minor: int patch: int
__post_init__ & Inheritance
Run extra validation/derivation after generated __init__ completes.
from dataclasses import dataclass, field@dataclassclass Rectangle: width: float height: float area: float = field(init=False) # computed, not passed to __init__ def __post_init__(self): self.area = self.width * self.height@dataclassclass Base: id: int@dataclassclass Derived(Base): name: str = "unnamed" # subclass fields must have defaults if base ones do
dataclass vs Alternatives
When to reach for dataclasses vs. NamedTuple or Pydantic.
- @dataclass- plain Python, no validation, fast, stdlib-only
- typing.NamedTuple- immutable, tuple-like, unpacks positionally, no methods overhead
- pydantic.BaseModel- runtime validation + coercion + JSON (de)serialization, extra dependency
- attrs- predecessor to dataclasses, more features (validators, converters)
- @dataclass(kw_only=True)- force keyword-only construction, avoids positional-arg mistakes
Introspection: fields(), asdict(), astuple(), replace()
Runtime helpers for inspecting a dataclass and producing modified copies without mutating the original.
from dataclasses import dataclass, fields, asdict, astuple, replace@dataclassclass Point: x: int y: intp = Point(1, 2)print(fields(p)) # tuple of Field objects: name, type, default, ...print(asdict(p)) # {'x': 1, 'y': 2} -- deep copy, recurses into nested dataclassesprint(astuple(p)) # (1, 2)p2 = replace(p, y=99) # new instance with y overridden, rest copiedprint(p2) # Point(x=1, y=99)
InitVar: Init-Only Pseudo-Fields
InitVar declares a constructor-only parameter that is passed to __post_init__ but never stored as an instance attribute.
from dataclasses import dataclass, InitVar, field@dataclassclass Circle: radius: float diameter: float = field(init=False) scale: InitVar[float] = 1.0 # accepted by __init__, forwarded to __post_init__, not a field def __post_init__(self, scale): self.diameter = self.radius * 2 * scalec = Circle(radius=5, scale=2)print(c.diameter) # 20.0print(hasattr(c, 'scale')) # False -- InitVar never becomes an instance attribute
Hashability Rules: eq, frozen & unsafe_hash
The combination of eq and frozen decides whether Python auto-generates __hash__, sets it to None, or leaves it inherited.
# eq (default True) + frozen determine hashability:# eq=True, frozen=False -> __hash__ set to None (unhashable, matches normal mutable-class semantics)# eq=True, frozen=True -> __hash__ generated from fields (safe: instance is immutable)# eq=False -> __hash__ inherited from object (identity-based)from dataclasses import dataclass@dataclass(eq=True, unsafe_hash=True) # force a field-based hash on a MUTABLE dataclassclass MutableButHashable: id: int# unsafe_hash=True generates __hash__ from fields even though frozen=False --# the name is a warning: mutating a field after hashing corrupts any set/dict# that already used the instance as a key or member
kw_only Fields & __match_args__
kw_only forces keyword-only construction; __match_args__ is auto-generated from positional (non-kw_only) fields for use with match/case.
from dataclasses import dataclass, field@dataclass(kw_only=True)class Config: host: str port: int = 8080 debug: bool = FalseConfig(host="localhost", port=9000) # Config("localhost", 9000) raises TypeError@dataclassclass Mixed: a: int b: int = field(kw_only=True) # per-field kw_only mixed with positional fields c: int = 0print(Mixed.__match_args__) # ('a', 'c') -- kw_only fields excluded from positional matchingmatch Mixed(1, c=2, b=3): case Mixed(x, y): # binds a and c positionally; b needs a keyword pattern print(x, y)
field() Parameter Reference
Every keyword argument field() accepts and what it controls in the generated methods.
- default- a fixed default value; mutually exclusive with default_factory
- default_factory- zero-arg callable invoked per-instance for mutable/complex defaults
- init- include this field as an __init__ parameter (default True)
- repr- include this field in the generated __repr__ (default True)
- hash- override whether the field participates in __hash__ (None defers to compare)
- compare- include this field in __eq__ and ordering methods (default True)
- metadata- read-only mapping attached to the Field object, ignored by dataclasses itself
- kw_only- force this specific field to be keyword-only, independent of the class-level setting
Never use a mutable default like members: list = [] directly on a field — Python evaluates it once at class definition time and all instances share it; always use field(default_factory=list) instead.