Dataclasses: OOP Without Boilerplate
Look at the Student class you've written for three lessons: __init__ assigning every field, __str__ formatting them, __eq__ comparing them — all of it mechanical. Python 3.7's @dataclass decorator writes all of that for you.
The before and after
The classic way — 15 lines of boilerplate:
class Student:
def __init__(self, name, age, marks):
self.name = name
self.age = age
self.marks = marks
def __str__(self):
return f"Student(name={self.name}, age={self.age}, marks={self.marks})"
def __eq__(self, other):
return (self.name == other.name and
self.age == other.age and
self.marks == other.marks)The dataclass way — the same behavior in 5 lines:
from dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
marks: float
s1 = Student("Aarav", 16, 92.0)
s2 = Student("Aarav", 16, 92.0)
print(s1) # Student(name='Aarav', age=16, marks=92.0)
print(s1 == s2) # TrueDeclare the fields with their types — the decorator generates __init__, __repr__, and __eq__ automatically. That's the deal: you describe the data; Python writes the plumbing.
Reading the field syntax
@dataclass
class Student:
name: str # field: type
age: int
marks: float = 0.0 # fields can have defaults (must come last)The name: str parts are type hints — labels saying what type each field should hold. Python doesn't enforce them at runtime (a str field will happily hold 5), but they document the class, power your editor's autocomplete, and catch mistakes in editors before running.
Methods mix in normally
Dataclasses don't replace methods — just the boilerplate:
@dataclass
class Student:
name: str
marks: list
def average(self):
return sum(self.marks) / len(self.marks)
s = Student("Aarav", [85, 90, 78])
print(s.average()) # 84.33...
print(s) # Student(name='Aarav', marks=[85, 90, 78])Generated __init__ + your own methods: the best of both.
Default values and default_factory
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
marks: list = field(default_factory=list) # mutable defaults done RIGHT
grade: str = "B" # simple default
s = Student("Aarav")
s.marks.append(90) # each student gets their OWN list
print(s) # Student(name='Aarav', marks=[90], grade='B')Remember the mutable-default trap from functions? Dataclasses solve it properly: mutable defaults use field(default_factory=list), which creates a fresh list per object.
When to use @dataclass vs a plain class
| Situation | Choice |
|---|---|
| Class mostly holds data (a record) | @dataclass — instant win |
| Data + a few computed methods | @dataclass + methods |
| Complex setup logic in __init__ | Plain class |
| Behavior-heavy, little data | Plain class |
Students, products, tasks, settings, API responses — the "records" of your programs are dataclass territory. This is also exactly how modern Python projects (and AI libraries) model their data.
Common Errors & Fixes
marks: float = 0.0 must come after required fields.field(default_factory=list), never = [].✅ Checkpoint
name: str lines called? *(Type hints — documented field types)*Module 9 checkpoint reached — classes, self, inheritance, dunders, dataclasses. You can now read and write the OOP that every framework speaks.
Next module: Power Tools — comprehensions, generators, and the Python that feels like Python.