Expert · Object-oriented programming · topic 39 of 50 · 30 min
Dataclasses and properties
Explanation
The boring part of classes
Many classes just hold data: a student, a product, a train ticket. For each one you write __init__ that copies every argument to self, a __repr__ for printing, and an __eq__ for comparing. That is a lot of repeated typing. The dataclasses module writes it for you.
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: int
qty: int = 1 # a default value
p = Product("Rice 5kg", 320, 2)
print(p) # Product(name='Rice 5kg', price=320, qty=2)
print(p == Product("Rice 5kg", 320, 2)) # True
print(Product("Salt", 25)) # qty defaults to 1@dataclass is a decorator: a line starting with @ placed above a class or function that adds extra behaviour to it. The types after the colons (str, int) are type hints. Python does not check them at run time, but they document your intent and help your editor find mistakes.
Lists as defaults: field(default_factory=list)
from dataclasses import dataclass, field
@dataclass
class Cart:
owner: str
items: list = field(default_factory=list) # a NEW list for every cart
def add(self, item):
self.items.append(item)
a = Cart("Asha")
b = Cart("Ravi")
a.add("Milk")
print(a.items, b.items) # ['Milk'] []Why not items: list = []? A default value is created once and would be shared by every object, the same trap you saw with class attributes. Dataclasses protect you: writing = [] raises ValueError and tells you to use default_factory.
frozen=True and order=True
from dataclasses import dataclass
@dataclass(frozen=True)
class Station:
code: str
name: str
ndls = Station("NDLS", "New Delhi")
try:
ndls.code = "BCT"
except Exception as e:
print(type(e).__name__) # FrozenInstanceError
print({ndls}) # frozen dataclasses can go in a set
@dataclass(order=True)
class Rank:
score: int
name: str
print(sorted([Rank(88, "Om"), Rank(95, "Ira"), Rank(88, "Ali")]))| Option | What it does | Use it when |
|---|---|---|
| frozen=True | Fields cannot be changed after creation; objects become hashable | Values that should never change: a station code, a date range |
| order=True | Adds <, <=, >, >= comparing fields like a tuple | You want to sort objects by their fields |
| field(default_factory=list) | A fresh list (or dict) for each object | A field that starts empty and grows |
@property: computed values and validation
A property looks like an attribute but runs a method. Use it for a value that is calculated from other fields, so it is never out of date. A property can also have a setter, which runs when you assign to it, so you can check the new value before storing it.
from dataclasses import dataclass
@dataclass
class CartItem:
name: str
price: int
qty: int
@property
def total(self): # computed every time, never stale
return self.price * self.qty
item = CartItem("Atta 10kg", 450, 2)
print(item.total) # 900 (no brackets)
item.qty = 3
print(item.total) # 1350
class Product:
def __init__(self, name, price):
self.name = name
self.price = price # goes through the setter below
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")
self._price = value
p = Product("Soap", 40)
try:
p.price = -5
except ValueError as e:
print(e)When to use a dataclass
- Use a dataclass when the class is mainly a bundle of named values, with maybe a few small methods.
- Use a normal class when the object is mostly behaviour with careful rules, like a bank account.
- Use a plain dictionary for quick, one-off data, such as a row you just read from a CSV file.
Watch the lesson
Dataclasses and properties · 1/7
In this lesson you will learn dataclasses, a quick way to write classes that mainly hold data.
Prefer a video by a teacher? Find free videos on “python dataclasses tutorial field default_factory property” on YouTube ↗
In real life: Train tickets in a booking app
A railway booking app has millions of tickets. Each ticket is a dataclass with a PNR, a passenger, a train number, a class and a fare. The station codes are frozen dataclasses because NDLS must never change. A @property called is_confirmed reads the status, so the screen and the SMS always agree.
from dataclasses import dataclass
@dataclass
class Ticket:
pnr: str
passenger: str
train: str
status: str = "WL 12"
@property
def is_confirmed(self):
return self.status.startswith("CNF")
t = Ticket("4521367890", "Passenger A", "12951")
print(t, t.is_confirmed)
t.status = "CNF B2 34"
print(t.is_confirmed)Practice (0/3 done)
Make Item a dataclass with fields name (str), price (int) and qty (int, default 1). Add a method total(self) that returns price times qty.
Make Cart a dataclass with a field owner (str) and a field items (a list) that starts empty for EACH cart. Add a method add(self, item) that appends to items.
Write a class Product with __init__(self, name, price). Make price a @property with a setter that raises ValueError if the new price is negative. Add a read-only property price_with_gst that returns price * 1.18 rounded to 2 decimals.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.