Chapters
Python114 chapters

Classes and objectsChapter 67 of 114

Dataclasses

Let Python write __init__, __repr__ and __eq__ for a class that holds data.

The boilerplate it removes

A class that is mostly fields needs an __init__ that copies each argument onto self, a __repr__ so it prints usefully, and an __eq__ so two equal records compare equal. @dataclass writes all three:

Python
from dataclasses import dataclass

@dataclass
class Dog:
    name: str
    age: int

rex = Dog("Rex", 3)
print(rex)
print(rex.name)
print(rex == Dog("Rex", 3))

Output

Dog(name='Rex', age=3)
Rex
True

That last line is the one you notice. A plain class compares by identity, so two identical dogs would be unequal.

The annotations are required

A dataclass reads the class body's annotations to find the fields. A bare assignment with no annotation is a class attribute, not a field:

Python
from dataclasses import dataclass, fields

@dataclass
class Dog:
    name: str
    age: int = 0
    species = "canis"

print([f.name for f in fields(Dog)])
print(Dog("Rex"))
print(Dog.species)

Output

['name', 'age']
Dog(name='Rex', age=0)
canis

The annotations are not enforced at runtime — same as anywhere else in Python — but here they are load-bearing for the generated code.

Defaults, and the mutable one

Fields with defaults come after those without, exactly as in a function:

Python
from dataclasses import dataclass, field

@dataclass
class Dog:
    name: str
    age: int = 0
    tricks: list = field(default_factory=list)

rex = Dog("Rex")
rex.tricks.append("sit")

print(rex)
print(Dog("Fido").tricks)

Output

Dog(name='Rex', age=0, tricks=['sit'])
[]

default_factory is called once per instance, so each dog gets its own list. Writing tricks: list = [] is rejected outright:

Python
from dataclasses import dataclass

try:
    @dataclass
    class Dog:
        tricks: list = []
except ValueError as problem:
    print("refused:", "mutable default" in str(problem))

Output

refused: True

This is a rare case of the language stopping you making the mutable-default mistake rather than letting it bite later.

Adding your own methods

A dataclass is an ordinary class. Everything else works as usual:

Python
from dataclasses import dataclass

@dataclass
class Rectangle:
    width: float
    height: float

    @property
    def area(self):
        return self.width * self.height

    def scaled(self, factor):
        return Rectangle(self.width * factor, self.height * factor)

r = Rectangle(3, 4)
print(r.area)
print(r.scaled(2))

Output

12
Rectangle(width=6, height=8)

__post_init__ for validation

Runs straight after the generated __init__, which is where checks and derived values go:

Python
from dataclasses import dataclass

@dataclass
class Dog:
    name: str
    age: int

    def __post_init__(self):
        if self.age < 0:
            raise ValueError("age cannot be negative")
        self.label = f"{self.name} ({self.age})"

print(Dog("Rex", 3).label)

try:
    Dog("Rex", -1)
except ValueError as problem:
    print("ValueError:", problem)

Output

Rex (3)
ValueError: age cannot be negative

frozen makes it immutable

Python
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int

p = Point(1, 2)
print(p)
print({Point(1, 2): "origin-ish"}[p])

try:
    p.x = 5
except Exception as problem:
    print(type(problem).__name__)

Output

Point(x=1, y=2)
origin-ish
FrozenInstanceError

Frozen dataclasses are hashable, so they work as dictionary keys and set members. Use them for value objects that should never change.

Ordering, and turning one into a dict

Python
from dataclasses import dataclass, asdict

@dataclass(order=True)
class Dog:
    age: int
    name: str

dogs = [Dog(5, "Rex"), Dog(2, "Fido")]
print(sorted(dogs))
print(asdict(dogs[0]))

Output

[Dog(age=2, name='Fido'), Dog(age=5, name='Rex')]
{'age': 5, 'name': 'Rex'}

order=True compares fields in the order they are declared, so put the one you want to sort by first. asdict gives you something json.dumps will accept.

Test yourself

2 questions

What does @dataclass write for you?

Show the answer

__init__, __repr__ and __eq__ — The __eq__ is the one you notice: a plain class compares by identity, so two identical records would be unequal.

What happens if you write 'tricks: list = []' in a dataclass?

Show the answer

Python refuses to build the class — It raises ValueError and tells you to use field(default_factory=list). A rare case of the language stopping the mistake.

Next chapter

Enums

A fixed set of named values, instead of loose strings scattered about.