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:
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:
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:
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:
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:
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:
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
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
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 questionsWhat 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.
Enums
A fixed set of named values, instead of loose strings scattered about.