BugForge
All lessons
Python · 05/10 6 min

Classes and dataclasses

Instance vs class state, default_factory, equality and hashing, properties and repr.

Dataclasses save boilerplate

from dataclasses import dataclass, field

@dataclass
class Order:
    id: int
    items: list[str] = field(default_factory=list)   # NOT `= []`
    total: int = 0

You get __init__, __repr__ and __eq__ for free. A plain = [] default raises ValueError at class creation, which is a good thing.

Equality and hashing

  • @dataclass compares field by field (__eq__), and by default makes instances unhashable (__hash__ = None): they can't go in a set or be dict keys.
  • @dataclass(frozen=True) makes them immutable and hashable.
  • A plain class compares by identity unless you define __eq__. If you define __eq__, define __hash__ consistently (or not at all).

Properties keep invariants

class Account:
    def __init__(self):
        self._balance = 0

    @property
    def balance(self):
        return self._balance       # read-only from outside

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("amount must be positive")
        self._balance += amount

Class methods and static methods

class Spot:
    @classmethod
    def from_code(cls, code): return cls(*code.split("-"))   # alternative constructor
    @staticmethod
    def is_valid(code): return "-" in code                   # no self or cls

Return copies of internal state

def history(self):
    return list(self._history)    # caller can't append to your internals

repr helps you debug

A good __repr__ turns <Order object at 0x10f…> into Order(id=3, total=900) in test failures. Dataclasses do it for you.

Next in Python
Collections for interviews