dataclass Basics
`@dataclass` writes the boring boilerplate for you: `__init__`, `__repr__`, and `__eq__`. You declare fields with type hints and (optionally) defaults, and Python builds the constructor and the value-equality semantics. This entry covers the basic shape, default values done right, and the `field()` escape hatch for mutable defaults.
531 views
5
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p1 = Point(3, 4)
p2 = Point(3, 4)
p3 = Point(0, 0)
print(p1) # Point(x=3, y=4)
print(p1 == p2) # True (value equality, not identity)
print(p1 == p3) # False
print(p1 is p2) # False (still two distinct objects)
# Attributes work like any other object.
p1.x = 10
print(p1) # Point(x=10, y=4)The decorator scans the class body for type-annotated names and turns them into fields. The synthesized __init__ takes the fields in declaration order (positional or keyword), __repr__ prints the class name with all field values, and __eq__ compares by field values, not identity. Two instances with the same field values are equal even though they are different objects. By default fields are still mutable (p1.x = 10 works); frozen=True makes them read-only, covered in the frozen-slots entry.
from dataclasses import dataclass, field
from typing import List
@dataclass
class Task:
title: str
priority: int = 3
# Wrong: `tags: List[str] = []` would share one list across every instance.
# Right: use field(default_factory=...) so each instance gets a fresh list.
tags: List[str] = field(default_factory=list)
t1 = Task('write docs')
t2 = Task('ship it', priority=1, tags=['release'])
t1.tags.append('writing')
t2.tags.append('done')
print(t1) # Task(title='write docs', priority=3, tags=['writing'])
print(t2) # Task(title='ship it', priority=1, tags=['release', 'done'])
# Each instance got its own tags list, no sharing.Fields without defaults must come before fields with defaults, same as a regular function signature. For mutable default values (list, dict, set), Python's class-attribute model would share one object across all instances, so dataclasses raise an error if you try to write tags: list[str] = []. The fix is field(default_factory=list): a zero-arg callable runs per instance and produces a fresh value. Use default_factory=dict for empty dicts, default_factory=lambda: 'pending' for any computed default.
from dataclasses import dataclass
@dataclass(order=True)
class Score:
points: int
name: str
leaderboard = [Score(40, 'ana'), Score(80, 'ben'), Score(40, 'cleo')]
leaderboard.sort() # sorts by points first, then name
print(leaderboard)
# [Score(points=40, name='ana'), Score(points=40, name='cleo'), Score(points=80, name='ben')]
@dataclass(repr=False)
class Token:
value: str
secret: str
def __repr__(self):
return f"Token(value='{self.value}', secret='***')"
print(Token('abc', 'sk-12345')) # Token(value='abc', secret='***')Decorator arguments toggle individual generated methods. order=True synthesizes <, <=, >, >= based on tuple comparison of fields in declaration order, which makes list.sort() and min/max Just Work. repr=False (or eq=False, init=False) suppresses one method when you want to write your own; the example uses it to redact a secret from repr. Other useful flags: kw_only=True to force keyword arguments, slots=True to skip __dict__ (covered in the frozen-slots entry).
