What you'll learn
Quick Answer
Decorate a class with @dataclass and declare fields with type annotations. You get __init__, __repr__ and __eq__ generated automatically. Mutable defaults must use field(default_factory=list) or every instance shares one object.
The boilerplate it removes
A plain class holding three fields needs the names written repeatedly:
class Student:
def __init__(self, name, marks=0, subjects=None):
self.name = name
self.marks = marks
self.subjects = subjects if subjects is not None else []
def __repr__(self):
return f"Student({self.name!r}, {self.marks!r}, {self.subjects!r})"
def __eq__(self, other):
return (self.name, self.marks, self.subjects) == \
(other.name, other.marks, other.subjects)
Every field appears four times. Adding one means editing three methods, and forgetting __eq__ is common — which is why comparing two objects that hold identical data often surprisingly returns False.
The same thing as a dataclass
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
marks: int = 0
subjects: list = field(default_factory=list)
That is the whole class, and it generates all three methods.
a = Student("Asha", 91)
print(a)
# Student(name='Asha', marks=91, subjects=[])
b = Student("Asha", 91)
print(a == b) # True
The __repr__ is genuinely useful — printing a plain object gives <__main__.Student object at 0x...>, which tells you nothing while debugging.
a == b being True is the generated __eq__ comparing field values. Without a dataclass that comparison is identity-based and returns False, which catches people out constantly in tests.
Note the annotations are required. A field without one is not picked up, which is a quiet way to lose a field from the generated methods.
The mutable default trap
This is the one rule you must know, and dataclasses will actually stop you breaking it.
@dataclass
class Bad:
subjects: list = [] # ValueError at class definition time
Python raises immediately: mutable default values are not allowed. That is a deliberate kindness — the equivalent mistake in a normal function signature fails silently and shares one list across every call.
@dataclass
class Good:
subjects: list = field(default_factory=list)
p, q = Good(), Good()
p.subjects.append("Physics")
print(q.subjects) # [] -- separate lists
default_factory takes a callable that is invoked per instance, so each object gets a fresh list. Use it for lists, dicts, sets and any object that could be mutated. It is the same underlying issue described in OOP in Python.
The options worth knowing
@dataclass(frozen=True)
class Point:
x: int
y: int
frozen=True makes instances immutable — assigning to a field raises. It also makes them hashable, so they can be dictionary keys or set members. For value objects such as coordinates or currency amounts this is usually what you want, and immutability removes a whole class of accidental-mutation bugs.
order=True generates comparison methods, so instances sort by their fields in declaration order.
field(repr=False) keeps a field out of the printed representation, which matters for anything sensitive — a password hash or token should not appear in logs because someone printed the object.
You can still add ordinary methods. A dataclass is a normal class with generated methods, not a restricted type.
When to use what
Use a dataclass for objects that primarily hold data with a few behaviours — configuration, records, DTOs, coordinates.
Use a NamedTuple when you want an immutable record that also behaves like a tuple, including unpacking. Lighter, but less flexible.
Use a plain dict when the shape is genuinely dynamic or comes straight from JSON and is passed through. A dataclass adds structure precisely when you want the field names fixed and checked.
Use pydantic when the data crosses a boundary and must be validated at runtime — API request bodies especially. Dataclasses do not validate; declaring marks: int does not stop a string being assigned, exactly as covered in type hints.
Dataclasses have been in the standard library since Python 3.7, so there is no dependency cost to using them.
