@dataclass1from dataclasses import dataclass2@dataclass3class Point:4 x: int5 y: int = 06p = Point(3)7print(p)8print(p == Point(3, 0)) y: list = []class PointValueErrordataclassclass Point3 methodsPoint(3)Point(x=3, y=0)Point(3, 0)
Code card: point.py; the highlighted line is the one running@dataclass: the decorator that writes the methodsClass Point: its method slots; object p: its field valuesTerminal: where print() writes@dataclass reads the annotated fields of a class and writes its __init__, __repr__ and __eq__ for you. Each step lights the parts it is about. A token hops along the step's arrow: the class into @dataclass, the new methods into the class's slots, Point(3) into the object p, p to the terminal as Point(x=3, y=0), and Point(3, 0) to __eq__, which prints True. In the last step line 5 becomes y: list = [] and dataclass() refuses with a ValueError: the class is never made.

Why dataclasses

  1. Many classes just hold data. Even a two-field Point needs __init__ to store the fields, __repr__ to show them and __eq__ to compare them. Writing these by hand is boilerplate: dull code you repeat.

How @dataclass works

Using the class

One rule

Try it yourself

;; step 1/9
class Point:                    # by hand, no @dataclass
    def __init__(self, x, y=0):
        self.x = x
        self.y = y
    def __repr__(self):
        return f"Point(x={self.x!r}, y={self.y!r})"
    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.x, self.y) == (other.x, other.y)

@dataclass also takes options: @dataclass(frozen=True) makes the fields read-only, order=True adds <, <=, > and >=, and slots=True (Python 3.10+) uses __slots__. The defaults are init=True, repr=True and eq=True. A field without a default cannot come after a field with one, just as in a def. The real traceback lists the lines inside dataclasses.py; the figure shortens them to ..., and wraps the last line to fit the terminal.