PythonDataclasses

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.

Python
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.

Python
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.

Python
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)
Warning
As with regular Python functions, once a field has a default, every field after it must also have a default — you can't follow a defaulted field with a required one.
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.

Python
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.

Python
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 instance
dataclasses.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.

Python
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 raises FrozenInstanceError.

  • order=True — adds tuple-style ordering comparisons between instances based on field order.

Tip
Dataclasses are still ordinary classes — you can add your own methods, properties, and even override the generated methods if needed. The decorator only fills in what you don't write yourself.
Note
The `dataclasses` module is part of the Python standard library, so no installation is required — just `from dataclasses import dataclass`.