Understanding dataclass from first principles
First: Why do we need dataclass?
Suppose we want to represent a user in our program.
We might write:
1
2
3
4
5
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
Now let’s think about what we’re actually doing.
We created a User object whose job is basically to hold data:
1
2
3
name
age
email
But we had to write quite a bit of boilerplate just to store those values.
And this gets worse as the class grows.
What problems do we face?
Imagine this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"User(name={self.name}, age={self.age}, email={self.email})"
def __eq__(self, other):
return (
self.name == other.name
and self.age == other.age
and self.email == other.email
)
Look at all that code.
But what is the class actually doing?
Mostly holding data.
We are writing a lot of repetitive code to tell Python:
“Hey, this object has these fields. Please initialize them, display them nicely, and compare them based on their values.”
So what’s the root problem?
The root problem is:
We’re writing repetitive boilerplate code for classes whose main purpose is to store data.
And this is where we naturally ask:
“Can Python generate all this boring code for us?”
Yes.
That’s what dataclass helps with.
So what is dataclass?
Python provides a built-in module called:
1
dataclasses
Inside it, there is something called:
1
dataclass
So we import it:
1
from dataclasses import dataclass
And then we can write:
1
2
3
4
5
6
7
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
That’s it.
We don’t need to manually write the __init__().
Python generates it for us.
What did we just gain?
Now we can do:
1
2
3
4
5
user = User(
name="Shoaib",
age=26,
email="shoaib@example.com"
)
And access the values:
1
2
print(user.name)
print(user.age)
We can also get a useful representation:
1
print(user)
Something like:
1
User(name='Shoaib', age=26, email='shoaib@example.com')
We didn’t write a __repr__() ourselves.
The dataclass generated it.
Let’s slow down and understand the magic
When Python sees:
1
2
3
4
5
@dataclass
class User:
name: str
age: int
email: str
you’re essentially telling Python:
“This class is mainly a container for data. Please generate the common methods I would otherwise have to write myself.”
So Python can automatically generate things such as:
__init__()→ creates/initializes the object__repr__()→ gives a useful string representation__eq__()→ compares objects based on their fields
There are also other options you can configure, such as ordering, immutability (frozen=True), and more.
But wait… what’s the difference from a normal class?
A normal class:
1
2
3
4
class User:
def __init__(self, name, age):
self.name = name
self.age = age
You manually tell Python:
“Create these attributes and assign these values.”
With a dataclass:
1
2
3
4
@dataclass
class User:
name: str
age: int
You tell Python:
“These are the fields this object has.”
Python figures out the repetitive parts.
Why are the : str and : int there?
This:
1
2
name: str
age: int
uses type annotations.
You’re saying:
1
2
name → expected to be a string
age → expected to be an integer
So:
1
2
3
4
@dataclass
class User:
name: str
age: int
is basically describing the shape of the data.
This is one reason dataclasses are especially nice when working with structured data.
A real-world analogy
Imagine you’re creating employee records.
Every employee has:
1
2
3
4
Name
Age
Department
Salary
Without dataclasses, you might repeatedly write forms like:
“Create name field, assign name. Create age field, assign age. Create department field…”
With a dataclass, you’re essentially saying:
“An employee record has these four fields.”
Python handles much of the repetitive machinery.
One important thing beginners should know
@dataclass does not mean:
“This is just a dictionary.”
It’s still a normal Python class.
You can still add methods:
1
2
3
4
5
6
7
8
9
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
def is_adult(self):
return self.age >= 18
Now:
1
2
3
user = User("Shoaib", 26)
print(user.is_adult())
So dataclasses are useful when you want:
A class + structured data + less boilerplate.
And what does the @ mean?
This part:
1
2
3
@dataclass
class User:
...
is a decorator.
You can think of it initially as:
“Take this class and apply some extra behavior to it.”
dataclass takes your class and modifies/enhances it by generating the methods you would commonly need.
You don’t need to deeply understand decorators yet to understand dataclasses.
The whole reasoning in one flow
Let’s connect the dots:
We need an object to store data.
↓
We create a class.
↓
The class mainly contains fields.
↓
We have to write repetitive __init__, __repr__, __eq__, etc.
↓
That’s boilerplate.
↓
So we ask:
“Why should we manually write code that Python can generate from the fields?”
↓
Python gives us:
1
from dataclasses import dataclass
↓
We write:
1
2
3
4
@dataclass
class User:
name: str
age: int
↓
Python generates the common boilerplate for us.
Mental model
Remember:
@dataclass= This class mainly holds data. Python, please handle the boring boilerplate.
And:
1
from dataclasses import dataclass
simply means:
“Bring Python’s
dataclasstool into this file so I can use it.”