Basic Dataclass
Decorate a class with @dataclass and annotate fields. Python generates __init__, __repr__, and __eq__ automatically.
from dataclasses import dataclass
# Without @dataclass (verbose)
class PersonManual:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name={self.name!r}, age={self.age!r})"
# With @dataclass (clean!)
@dataclass
class Person:
name: str
age: int
p = Person("Alice", 30)
print(p) # Person(name='Alice', age=30)
print(p.name) # Alice
print(Person("Bob", 25) == Person("Bob", 25)) # TrueDefault Values and field()
Use field() for mutable defaults (lists, dicts). Never use mutable defaults directly.
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
grade: float = 0.0
courses: list = field(default_factory=list) # Correct!
# courses: list = [] ← WRONG — shared across instances!
s1 = Student("Alice")
s2 = Student("Bob")
s1.courses.append("Python")
print(s1.courses) # ['Python']
print(s2.courses) # [] — independent list!__post_init__ – Computed Fields
Run code after __init__ for validation or computed attributes.
from dataclasses import dataclass, field
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False) # Not a constructor param
def __post_init__(self):
if self.width <= 0 or self.height <= 0:
raise ValueError("Dimensions must be positive")
self.area = self.width * self.height
r = Rectangle(5, 3)
print(r) # Rectangle(width=5, height=3, area=15)
print(r.area) # 15Frozen Dataclasses – Immutable Objects
frozen=True makes all fields read-only and the object hashable (usable as dict key).
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(3.0, 4.0)
print(p) # Point(x=3.0, y=4.0)
try:
p.x = 10 # AttributeError!
except Exception as e:
print(e)
# Hashable — can be dict key or set member
locations = {p: "Home"}
print(locations[Point(3.0, 4.0)]) # Home@dataclass: Kill the Boilerplate
A @dataclass auto-generates __init__, __repr__, and __eq__ from your field annotations. It turns a 15-line class into 4 — the pythonic way to write a "just holds data" class.
from dataclasses import dataclass, field
@dataclass
class Point:
x: int
y: int = 0 # default value
tags: list = field(default_factory=list) # ⚠️ NOT tags=[]
p = Point(1, 2)
print(p) # Point(x=1, y=2, tags=[]) ← free __repr__
Point(1, 2) == Point(1, 2) # True — free __eq__
The mutable-default trap (again): you can't write tags: list = [] — that shares one list across all instances, and dataclasses actually raise an error to stop you. Use field(default_factory=list) to build a fresh one per instance.
| Option | Effect |
|---|---|
@dataclass(frozen=True) | immutable + hashable (usable as dict key) |
@dataclass(order=True) | adds <, > comparisons |
field(repr=False) | hide a field from __repr__ |
Reach for a dataclass over a plain class when the object is mostly fields; over a NamedTuple when you need mutability or methods.
🏋️ Practical Exercise
Use the @dataclass decorator:
- Create a
Pointdataclass withxandyfields and print an instance. - Add a default value to one field and a list field using
field(default_factory=list). - Add a
__post_init__that computes a derived field. - Create a
frozen=Truedataclass and confirm its instances are immutable.
🔥 Challenge Exercise
Model an Order dataclass with a customer name, a list of line items, and a computed total filled in by __post_init__. Use field(default_factory=list) for the items so each order gets its own list. Make a frozen Money dataclass and use it inside the order to demonstrate immutability and automatic __eq__. Bonus: sort a list of orders by total using the auto-generated comparison via order=True.
📋 Summary
- The
@dataclassdecorator auto-generates__init__,__repr__, and__eq__from class-level field annotations. - Default values are allowed; mutable defaults must use
field(default_factory=...)to avoid shared state. __post_init__runs after the generated__init__for validation or computed fields.frozen=Truemakes instances immutable and hashable.order=Truegenerates comparison methods so instances can be sorted.- Dataclasses are ideal for simple data-holding classes, reducing boilerplate dramatically.
Interview Questions on Dataclasses
- What is a dataclass and what boilerplate does it remove?
- Which dunder methods does
@dataclassgenerate automatically? - Why must mutable defaults use
field(default_factory=...)? - What is
__post_init__used for? - What does
frozen=Truedo? - How do dataclasses compare to named tuples and regular classes?
- What does the
order=Trueparameter add?
Related Topics
FAQ
A bare default like items: list = [] would be shared by every instance, just like a mutable default argument. Dataclasses raise an error and require field(default_factory=list), which creates a fresh list per instance.
Named tuples are immutable and tuple-based, good for lightweight records. Dataclasses are mutable by default (unless frozen), support methods and defaults more flexibly, and are usually clearer for richer data objects.
__post_init__ run? +Immediately after the auto-generated __init__ assigns the fields. Use it for validation or to compute derived attributes from the provided fields.
frozen=True make the object fully immutable? +It blocks reassigning fields, raising FrozenInstanceError, and makes the instance hashable. However, a mutable field value (like a list inside it) can still be modified in place.

