DMarlon Studio

Lesson 11 · 11 of 20

Dataclasses · Python

Python dataclasses with @dataclass: free __init__, __repr__ and __eq__, field(default_factory), __post_init__, frozen and order.

Python · Dataclasses in DMarlon Studio: the example's code and its output in the terminal

Many classes only hold data. With the `@dataclass` decorator you just declare the fields with their types (product: str, quantity: int…) and Python writes __init__, __repr__ and __eq__ for you. That is why print(inv.lines[0]) is readable and two Line objects with the same data are equal with ==.

Fields with defaults go last: paid: bool = False. For a list, lines: list = [] will not do, because every invoice would share the same one; `field(default_factory=list)` creates a fresh list for each object.

`__post_init__` runs right after the generated __init__ and is the place to validate or clean data: here it strips the spaces from the customer name and title-cases it. Dataclasses can have ordinary methods too, like total().

@dataclass(frozen=True, order=True) gives Version two extras: `frozen` makes the objects read-only (assigning a field raises an error) and `order` compares them field by field, like tuples. So max() knows that 1.10 is newer than 1.9, something a plain text comparison would get wrong.

asdict(inv) turns the invoice, lines included, into plain dictionaries: the usual first step before saving it as JSON.

Code · main.py

from dataclasses import dataclass, field, asdict@dataclassclass Line:    product: str    quantity: int    unit_price: float@dataclassclass Invoice:    number: str    customer: str    lines: list[Line] = field(default_factory=list)   # a new list each time    paid: bool = False    def __post_init__(self):          # runs right after the generated __init__        self.customer = self.customer.strip().title()    def total(self):        return sum(l.quantity * l.unit_price for l in self.lines)@dataclass(frozen=True, order=True)  # immutable and comparableclass Version:    major: int    minor: intinv = Invoice("F-2026-014", "  maria lopez ")inv.lines.append(Line("Chair", 4, 49.5))inv.lines.append(Line("Table", 1, 230.0))print(inv.lines[0])                   # free __repr__print(inv.customer, "owes", inv.total(), "| paid:", inv.paid)print("Equal:", Line("Chair", 4, 49.5) == inv.lines[0])   # free __eq__print(asdict(inv)["lines"][1])print("Newest:", max(Version(1, 9), Version(1, 10), Version(0, 99)))

Output · python3 main.py

Line(product='Chair', quantity=4, unit_price=49.5)
Maria Lopez owes 428.0 | paid: False
Equal: True
{'product': 'Table', 'quantity': 1, 'unit_price': 230.0}
Newest: Version(major=1, minor=10)

Key points

  • @dataclass generates __init__, __repr__ and __eq__.
  • Use field(default_factory=list) for default lists and dicts.
  • __post_init__ validates or adjusts data on creation.
  • frozen=True makes it immutable; order=True, comparable.
  • asdict() turns it into a dictionary.

Exercise

Add a discount: float = 0.0 field to Line, make Invoice.total() take it into account and give the chairs a 10% discount.