Why dataclasses
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
Line 1 imports dataclass from the standard library. It is a decorator: a function that takes a class, changes it and gives it back. Writing @dataclass above a class applies it.
Lines 4 and 5 are class variables with a type annotation, name: type. Each one becomes a field, in order: x, then y. The = 0 gives y a default. Python does not check the types.
Python first builds the class from lines 3 to 5. Then @dataclass on line 2 hands it to dataclass(), which reads its fields: x with no default, y with the default 0.
From the field list, dataclass() writes __init__, __repr__ and __eq__ and adds them to Point. It gives back the same class, now complete, and the name Point refers to it.
Using the class
Point(3) calls the generated __init__(self, x: int, y: int = 0). It sets self.x = 3; y was left out, so it takes the default 0. The new object is stored under the name p.
Point has no __str__, so print() falls back to the generated __repr__: the class name, then each field as name=value, Point(x=3, y=0). A plain class prints something like <__main__.Point object at 0x...>.
p == Point(3, 0) calls the generated __eq__. It compares the fields in order, like tuples: (3, 0) == (3, 0), so True. A plain class says two objects are equal only if they are the same object.
One rule
Change line 5 to y: list = []. That one list would be shared by every Point, so dataclass() refuses with a ValueError and the class is never made. field(default_factory=list) gives each object a new list.
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.