§ 2.2section 2 of 20·~5 min
Dataclasses (you'll use these constantly)
Most classes you write are mainly containers of data. Writing __init__, __repr__ and __eq__ by hand for each one is boring and error-prone. @dataclass generates them from a list of typed fields:
from dataclasses import dataclass, field
@dataclass
class Book:
isbn: str
title: str
author: str
issued_to: str | None = None # optional, with a default
tags: list[str] = field(default_factory=list) # a mutable default, done safely
@property
def available(self) -> bool: # normal methods and properties still work
return self.issued_to is None
b = Book("978-81", "Wings of Fire", "A.P.J. Abdul Kalam")
print(b) # Book(isbn='978-81', title='Wings of Fire', author='A.P.J. Abdul Kalam', issued_to=None, tags=[])
b == Book("978-81", "Wings of Fire", "A.P.J. Abdul Kalam") # True
Rules and useful options:
- Fields without defaults must come before fields with defaults.
- For list or dict defaults, use
field(default_factory=list). You'll remember the mutable-default trap from Week 1:tags: list = []would be shared by every book. Dataclasses refuse to let you write it. @dataclass(frozen=True)makes objects immutable, which suits records that must never change, like a transaction.dataclasses.asdict(b)turns an object into a plain dict, ready forjson.dumps.Book(**d)turns a dict back into an object. The lab'sstorage.pyuses exactly these two moves.
Looking ahead: in Week 3 you'll meet Pydantic BaseModel, which looks almost identical but also validates the data (it rejects amount="abc"). Pydantic models are how FastAPI defines requests and how LLMs are told to return structured output.