Na dit hoofdstuk snap je:
- wat een dataclass is en waarom het bestaat,
- hoe
@dataclassautomatisch__init__,__repr__,__eq__(en meer) voor je maakt, - hoe je default values doet (en waarom
default_factorybelangrijk is), - wat
frozen=Truebetekent (immutability + hashing), - en wanneer je juist géén dataclass moet gebruiken.
1. Waarom bestaan dataclasses? #
Je merkt het snel als je veel “data-objecten” hebt:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)PythonDit is veel herhaalwerk voor iets simpels.
Een dataclass doet dat voor je.
2. Eerste dataclass (veel korter, zelfde resultaat) #
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p = Point(1, 2)
print(p) # Point(x=1, y=2)
print(p == Point(1, 2)) # TruePythonWat je “gratis” krijgt:
__init__(x, y)__repr____eq__- en handige extras zoals makkelijk vergelijken/printen
Belangrijk: die x: int en y: int zijn type hints. Ze zijn niet verplicht, maar dataclasses werken er heel goed mee (en tooling ook).
3. Default values (simpel) #
from dataclasses import dataclass
@dataclass
class User:
name: str
is_admin: bool = False
print(User("Alice"))
# User(name='Alice', is_admin=False)Python4. Mutable defaults: waarom field(default_factory=...) bestaat #
Net als bij functies en __init__ geldt:
Gebruik geen
[]of{}direct als default, want dan deel je dezelfde lijst/dict.
Fout
from dataclasses import dataclass
@dataclass
class Team:
members: list[str] = [] # ❌ gedeelde lijstPythonGoed
from dataclasses import dataclass, field
@dataclass
class Team:
members: list[str] = field(default_factory=list)PythonNu krijgt elke Team() zijn eigen lijst.
5. Validatie en afgeleide velden: __post_init__ #
Een dataclass maakt automatisch __init__, maar soms wil je extra checks of conversies.
Daarvoor is __post_init__: die draait na de auto-generated __init__.
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
def __post_init__(self):
if self.price < 0:
raise ValueError("Price cannot be negative")
self.price = float(self.price)PythonWaarom niet alles in properties?
- Kan wel, maar
__post_init__is vaak de simpelste plek voor “init-validatie”.
6. Immutability: frozen=True #
Soms wil je objecten die niet mogen veranderen (handig voor value objects).
from dataclasses import dataclass
@dataclass(frozen=True)
class Money:
currency: str
amount: intPythonNu kan dit niet:
m = Money("EUR", 10)
m.amount = 20 # Geef fout: FrozenInstanceErrorPythonWaarom is dit handig?
- Minder bugs: state verandert niet stiekem.
- Je kunt zulke objecten vaak veilig als dict-key gebruiken (hashing).
7. Hashing (kort maar belangrijk) #
Als je frozen=True gebruikt, kan Python meestal een __hash__ genereren die klopt bij __eq__.
Daardoor kan je Money("EUR", 10) in een set stoppen of als key gebruiken.
Als je object wél mutable is, is dat gevaarlijk → daarom is hashing vaak uitgeschakeld.
8. Wanneer gebruik je een dataclass (en wanneer niet)? #
Dataclass is top voor: #
- “data carriers”: DTO’s, config objects, simpele domeinobjecten
- value objects:
Point,Money,Address - objecten waar je vooral velden hebt en weinig complex gedrag
Liever geen dataclass als: #
- je class vooral gedrag heeft en weinig data
- je zware invarianten hebt die je constant moet bewaken (dan past property/encapsulation vaak beter)
- je init heel custom is (kan nog steeds met dataclass, maar dan verlies je de eenvoud)
Praktisch: begin gerust met dataclass voor simpele dingen. Als je class complexer wordt, kun je altijd refactoren.
Hartstikke goed, nu heb je de meeste concepten van OOP gehad. Zie het laatste hoofdstuk om de toepasbaarheid nog beter te leren.

