Dataclasses: less boilerplate, same data model
Let @dataclass write __init__, __repr__ and __eq__ for you, give fields safe defaults with field(), and make objects hashable and sortable: frozen, order.
The last two lessons wrote special methods by hand: __init__ to store the fields, __repr__ to show them, __eq__ and __hash__ to compare them. For a class that mainly holds data, that's a lot of typing, and easy to get subtly wrong. @dataclass writes them for you, from a list of fields with type annotations:
from dataclasses import dataclass, field
@dataclass
class Parcel:
to: str
weight_kg: float
fragile: bool = False
labels: list[str] = field(default_factory=list)
p = Parcel("Oslo", 2.5)
print(p) # a useful repr, for free
print(p == Parcel("Oslo", 2.5)) # True: __eq__ compares the fieldsParcel(to='Oslo', weight_kg=2.5, fragile=False, labels=[]) True
It's still an ordinary class: you can add your own methods, and nothing checks the types at run time (the annotations are for you and your tools). A line needs an annotation to become a field: weight_kg = 0 without one is a plain class attribute.
Safe defaults with field()
Remember the mutable default trap? Dataclasses won't let you fall into it. labels: list[str] = [] stops with a ValueError the moment the class is defined. field(default_factory=list) calls list() for each new object instead, so no two parcels share a list. Fields with defaults must come after the ones without, like a function's parameters.
frozen=True: hashable, because it can't change
A plain dataclass compares by value, so, just like the Seat class in the last lesson, it gets __hash__ = None: it can't be a dict key. Ask for frozen=True and the fields can't be changed after the object is made, so Python can safely give it a hash built from them:
from dataclasses import dataclass
@dataclass(frozen=True)
class Colour:
red: int
green: int
blue: int
teal = Colour(0, 128, 128)
names = {teal: "teal"}
print(names[Colour(0, 128, 128)]) # teal: equal colours, equal hashes
print(Colour.__hash__ is not None) # TrueTry teal.red = 255 after that, and you get a FrozenInstanceError.
order=True: sorting for free
order=True adds <, <=, > and >=. Objects compare as if they were tuples of their fields, in the order the fields are written: the first field decides, and only a tie moves on to the next one. That makes sorted(), min() and max() work.
More in the Python docs: dataclasses, field(), mutable default values and frozen instances.
Your turn
Write a dataclass Version with three int fields, major, minor and patch (patch defaults to 0). Make it frozen, so versions can be dict keys, and ordered, so that the starting code’s sorted() and max() work: 2.1.0 is newer than 2.0.7, which is newer than 1.9.4.
Your task
Write a dataclass Version with three int fields, major, minor and patch (patch defaults to 0). Make it frozen, so versions can be dict keys, and ordered, so that the starting code’s sorted() and max() work: 2.1.0 is newer than 2.0.7, which is newer than 1.9.4.
- Version(2, 1) shows its three fields, patch 0 (not done yet)
- Versions sort by major, then minor, then patch (not done yet)
- Frozen: equal versions hash the same (not done yet)
- Print the newest release (not done yet)
The checklist ticks itself off as you work in the editor: any way that gets the result counts.
Hints come one at a time, then one way to do it. Try each before the next.
What this lesson uses, in one place.
@dataclass- Writes __init__, __repr__ and __eq__ from a class’s annotated fields Python docs →
field(default_factory=…)- A fresh default for each object, such as field(default_factory=list) Python docs →
frozen=True- Fields can’t be changed after the object is made, and the class gets a __hash__ Python docs →
order=True- Adds < <= > >=, comparing the fields in order, like tuples Python docs →