Dataclasses and namedtuples
Structured records without the boilerplate
__init__, __repr__, __eq__ and more — is generated for you from type-annotated attributes. A namedtuple is an immutable tuple whose positions also have names. Both let you model a small bundle of related fields clearly; the rest of this topic is about picking the right one and driving it with field() and frozen=True.
- replace hand-written
__init__/__repr__/__eq__with@dataclass, - give fields defaults safely, including mutable defaults via
field(default_factory=...), - make records immutable and hashable with
frozen=True, - tune individual fields with
field()(repr,compare,init,kw_only), - choose between a dataclass, a
NamedTuple, a plain class and a dict for a given job.
1. The boilerplate problem
You already know how to write a small record class by hand. The trouble is that even a three-field record forces you to repeat every name three or four times just to get sensible construction, printing and equality.
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
That is a lot of ceremony for "an x and a y". Miss the __eq__ and two equal points compare unequal; miss the __repr__ and your logs show <Point object at 0x...>. Dataclasses generate exactly this plumbing from the annotations.
2. @dataclass in one line
Decorate a class with @dataclass and list its fields as class-level annotations. The decorator reads those annotations and writes __init__, __repr__ and __eq__ for you at class-creation time.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p = Point(1.0, 2.0)
print(p) # Point(x=1.0, y=2.0)
print(p == Point(1.0, 2.0)) # True
The annotation is what matters: x: float declares a field. A bare x = 0 without an annotation is not a field — it is an ordinary class attribute the dataclass machinery ignores. The type is not enforced at runtime; it is documentation that tools and readers use, and the marker that turns a name into a field.
By default you also get keyword or positional construction (Point(x=1, y=2)), a readable repr, and value equality. You do not get ordering (<, >) or hashing yet — those are opt-in, covered below.