Data-holding classes are everywhere, and writing their boilerplate by hand is tedious and error-prone.
A dataclass is a normal class whose special methods are generated from its annotated class attributes:
@dataclass
class Point:
x: float
y: floatFrom these two lines you get:
__init__(self, x, y)that stores the arguments as attributes, in field order,__repr__such asPoint(x=1, y=2), great for debugging and logs,__eq__that compares all fields, soPoint(1, 2) == Point(1, 2)isTrue(a plain class would compare identity).
Only names with a type annotation become fields. A class attribute without an annotation is an ordinary class attribute, and an annotation like ClassVar[int] is excluded on purpose.
The annotations are not enforced: Point("a", None) works at runtime. Type checkers use them, and libraries like Pydantic add runtime validation if you need it.
Everything else about the class is normal: you can add methods, properties, class methods and docstrings, and subclass it. A dataclass is not a special kind of object; the decorator just writes methods you would otherwise type by hand.
Example
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
def distance_to_origin(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
p = Point(3, 4)
print(p) # Point(x=3, y=4)
print(p.distance_to_origin()) # 5.0
print(p == Point(3, 4)) # True (field-by-field)
print(p is Point(3, 4)) # False
print(Point("a", None)) # Point(x='a', y=None) types are not enforcedRemember
- @dataclass generates __init__, __repr__ and __eq__ from annotated fields
- Only annotated class attributes become fields
- Type hints are not checked at runtime
Watch out for
- Forgetting the annotation (x = 0 instead of x: int = 0), so the attribute silently isn't a field.