Dataclasses
Many classes exist purely to hold a bundle of related values — a point, a coordinate, a configuration record. Writing __init__, __repr__, and __eq__ by hand for classes like this is repetitive and easy to get subtly wrong. Since Python 3.7, the dataclasses module provides the @dataclass decorator, which generates all of that boilerplate for you from a short list of type-annotated fields.
BEFORE: A Plain Class Written by Hand
Here's a Point class written the traditional way. To support printing and equality comparisons, you need to write __init__, __repr__, and __eq__ explicitly — three methods, each a chance to introduce a typo or forget a field when you add a new one later.
class Point:
def __init__(self, x, y):
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 == other.x and self.y == other.y
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1)
print(p1 == p2)Point(x=1, y=2) True
AFTER: The Same Class with @dataclass
With @dataclass, you declare the fields as type-annotated class attributes and the decorator writes __init__, __repr__, and __eq__ for you automatically, matching the field order and names exactly.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1)
print(p1 == p2)Point(x=1, y=2) True
Same behavior, a fraction of the code. Adding a third field later means adding one line, not touching three separate methods.
Field Defaults
Fields can have default values, just like function parameters — give one a default and callers can omit it.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int = 0
print(Point(5))
print(Point(5, 9))Point(x=5, y=0) Point(x=5, y=9)
Mutable Defaults: field(default_factory=...)
You might expect to write tags: list = [] to give a field an empty list by default. This fails, and for the same reason mutable default arguments are a classic Python function pitfall: a single list object would be created once and shared across every instance of the class, so appending to one instance's tags would silently affect all the others. @dataclass actually detects this specific case for lists, dicts, and sets and raises an error at class-definition time rather than letting the bug slip through silently.
The fix is field(default_factory=list), which tells the dataclass to call list() fresh for every new instance instead of reusing one shared object. This needs an extra import from the dataclasses module.
from dataclasses import dataclass, field
@dataclass
class Cart:
owner: str
tags: list = field(default_factory=list)
a = Cart("Alice")
b = Cart("Bob")
a.tags.append("urgent")
print(a.tags)
print(b.tags) # unaffected — each instance got its own list['urgent'] []
Immutability with frozen=True
Passing frozen=True to the decorator makes instances immutable after creation — any attempt to assign to a field raises FrozenInstanceError. This is useful for value-like objects that should never change once constructed, such as coordinates used as dictionary keys.
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
p.x = 99 # attempting to mutate a frozen instancedataclasses.FrozenInstanceError: cannot assign to field 'x'
Automatic Ordering with order=True
Passing order=True generates comparison methods for less-than, less-than-or-equal, greater-than, and greater-than-or-equal, based on comparing the fields in the order they were declared — as if comparing tuples of the field values. That means dataclass instances become sortable immediately.
from dataclasses import dataclass
@dataclass(order=True)
class Point:
x: int
y: int
points = [Point(3, 1), Point(1, 5), Point(1, 2)]
print(sorted(points))[Point(x=1, y=2), Point(x=1, y=5), Point(x=3, y=1)]
Points are compared first by x, and ties are broken by y — exactly like comparing tuples (x, y).
Common @dataclass Options
@dataclass— generates__init__,__repr__, and__eq__from the annotated fields.field(default_factory=...)— gives a mutable field (list, dict, set) a fresh default per instance instead of one shared object.frozen=True— makes instances immutable; assignment after construction raisesFrozenInstanceError.order=True— adds tuple-style ordering comparisons between instances based on field order.