Pydantic vs Dataclasses: What I Actually Use in Real Code
1. The Problem.
Pydantic vs Dataclasses: What I Actually Use in Real Code
The Source of the Image
1. The Problem.
2. First Solution With Dataclasses.
3. Second Solution With Pydantic.
- Type Validation at Creation
- Type Validation on Assignment
- Custom Validation Rules
- Special Types
- Easy Export to JSON
- Dictionary Conversion
- Equality Works
4. When to Use DataClasses Vs Pydantic.
5. Before and After.
6. Conclusion.
Introduction
You’ve written this code hundreds of times. A class that handle some data. Customer information, user profiles, product details. The same pattern over and over:
class Customer:
def __init__(self, name, id, surname):
self.name = name
self.id = id
self.surname = surname
It works. But it’s boring. And it has deficiencies you ignore until they hit you. Try comparing two clients with similar data. They aren’t equal. Python recognises two distinct objects, not two identical records. Print out a customer for debugging. Instead of the real data, you get an useless memory address. Play with invalid data, like typing a string instead of a number. Your code takes it silently and then breaks somewhere inside in your program, where the error recognition is ineffective.
So you add more code. An __eq__ method for comparisons. A __repr__ method for debugging. Maybe some validation in __init__ to check types. Before you know it, your simple data class contains 30 lines of boilerplate. And you must code this for every structure of data in your application. This isn’t a little problem. Modern applications store information everywhere. Between functions, over API boundaries, into databases, and out as JSON. If you define a new data structure, you have to choose either to write all that boilerplate or ship brittle code that breaks in production.
There is a better way. In fact, there are two more effective approaches, and the best one will depend on what you’re developing. Python’s dataclasses module dramatically decreases programming repetition. One decorator offers identity verification, clear output, and neat syntax. Your class, which was formerly 30 lines long, is now seven lines long. It’s integrated into Python version 3.7 and afterwards, so there’s nothing further to install. But dataclasses do not validate data. They believe everything you pass in. That is fine for internal programs in which you have control over the data flow. It’s not acceptable for processing user input, decoding API replies, or reading configuration files. This is why Pydantic comes in.
Pydantic models resemble dataclasses but include runtime validation. Pass a string that does require an integer? Pydantic detects it promptly and displays an obvious error message. Try assigning faulty data after creation. Pydantic stops you. Do you need to validate an email’s formatting, URL structure, or unique business rules? Pydantic handles it. Additionally, it provides simple JSON serialisation. The distinction between these methods is not academic. Choose dataclasses around internal data formats that allow you to govern the inputs. Pydantic is ideal for validating boundaries, including APIs, user input, and other data sources. Choose standard classes only if you require custom behaviour beyond data storage.
This post demonstrates both techniques using real code. You’ll examine the issues with normal classes, how dataclasses address the majority of them, and why Pydantic’s validation is justified the additional dependency. By the end, you’ll have written less garbage and delivered more resilient software.
Let’s start with the problem.
The Problem
You write a lot of classes that just hold data. Customer records, user profiles, API responses. Each time, you write the same boring code:
class Customer:
def __init__(self, cus_name, cus_id, cus_last_name):
self.name = cus_name
self.id = cus_id
self.surname = cus_last_name
This works. But it has problems. Try comparing two customers:
ob_1 = Customer("zaid", 1, "alissa")
ob_2 = Customer("zaid", 1, "alissa")
print(ob_1 == ob_2) # False
They’re not equal, even though they have identical data. Python compares object identity, not values. Try printing one:
print(ob_1) # <__main__.Customer object at 0x7f8b...>
Useless for debugging. You see a memory address, not the actual data. And there’s no validation. Someone can pass id="oops" and your code breaks later when you try to use it as a number.
First Solution With Dataclasses
Python’s dataclasses fix most of this with one decorator:
from dataclasses import dataclass
@dataclass
class CustomerDataClass:
name: str
id: int
surname: str
Now look what happens:
ob_3 = CustomerDataClass("zaid", 1, "alissa")
ob_4 = CustomerDataClass("zaid", 1, "alissa")
print(ob_3) # CustomerDataClass(name='zaid', id=1, surname='alissa')
print(ob_3 == ob_4) # True
What you get:
- Proper
__init__method - Readable
__repr__for debugging - Equality comparisons that work
- Less code to maintain
Example with more fields:
@dataclass
class Person:
name: str
age: int
height: float
email: str
person = Person('Zaid', 11, 1.11, 'zaid@zaidalissa.me')
print(person)
# Person(name='Zaid', age=11, height=1.11, email='zaid@zaidalissa.me')
Clean. Simple. Done.
Second Solution With Pydantic
Dataclasses are great for internal code. But what if you’re handling external data? User input, API calls, config files. You need validation.
That’s where Pydantic comes in:
from pydantic import BaseModel, EmailStr, field_validator, ValidationError, ConfigDict
class CustomerModel(BaseModel):
name: str
id: int
surname: str
model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True)
@field_validator('name', 'surname')
@classmethod
def not_empty(cls, v: str) -> str:
if not v:
raise ValueError("must not be empty")
return v
What this gives you:
- Type Validation at Creation
try:
CustomerModel(name="zaid", id="oops", surname="alissa")
except ValidationError as e:
print(e)
# ValidationError: id must be an integer
Bad data gets caught immediately, not later when your code breaks without warning.
- Type Validation on Assignment
cm = CustomerModel(name="zaid", id=1, surname="alissa")
try:
cm.id = "2"
except ValidationError as e:
print(e)
# ValidationError: id must be an integer
With validate_assignment=True, even changes after creation get validated.
- Custom Validation Rules
try:
CustomerModel(name="", id=1, surname="alissa")
except ValidationError as e:
print(e)
# ValidationError: name must not be empty
You define rules once. Pydantic enforces them everywhere.
- Special Types
from pydantic import EmailStr
class PersonModel(BaseModel):
name: str
age: int
height: float
email: EmailStr
model_config = ConfigDict(validate_assignment=True)
p = PersonModel(name="Zaid", age=11, height=1.11, email="zaid@zaidalissa.me")
print(p)
# PersonModel(name='Zaid', age=11, height=1.11, email='zaid@zaidalissa.me')
EmailStr validates email format automatically. No regex needed. Try a bad email:
try:
PersonModel(name="Zaid", age=11, height=1.11, email="not-an-email")
except ValidationError as e:
print(e)
# ValidationError: email is not a valid email address
- Easy Export to JSON
print(p.model_dump_json(indent=2))
{
"name": "Zaid",
"age": 11,
"height": 1.11,
"email": "zaid@zaidalissa.me"
}
Perfect for APIs and saving data.
- Dictionary Conversion
print(p.model_dump())
# {'name': 'Zaid', 'age': 11, 'height': 1.11, 'email': 'zaid@zaidalissa.me'}
- Equality Works
cm_1 = CustomerModel(name="zaid", id=1, surname="alissa")
cm_2 = CustomerModel(name="zaid", id=1, surname="alissa")
print(cm_1 == cm_2) # True
When to Use DataClasses Vs Pydantic
Use regular classes when:
- You’re prototyping
- The class has behavior, not just data
- Performance is critical (though the difference is usually tiny)
Use dataclasses when:
- You’re holding data internally
- You want clean, readable code
- You don’t need validation
- You’re working with Python 3.7+
Use Pydantic when:
- You’re handling external data (APIs, user input, config files)
- You need validation
- You’re building web APIs (FastAPI uses Pydantic)
- You want automatic documentation
- You need JSON serialization
Before and After
Before (regular class):
class Person:
def __init__(self, name, age, height, email):
self.name = name
self.age = age
self.height = height
self.email = email
def __repr__(self):
return f'Person(name={self.name}, age={self.age}, height={self.height}, email={self.email})'
person = Person('Zaid', 11, 1.11, 'zaid@zaidalissa.me')
12 lines of boilerplate.
After (dataclass):
@dataclass
class Person:
name: str
age: int
height: float
email: str
person = Person('Zaid', 11, 1.11, 'zaid@zaidalissa.me')
7 lines. Same result.
After (Pydantic with validation):
class PersonModel(BaseModel):
name: str
age: int
height: float
email: EmailStr
model_config = ConfigDict(validate_assignment=True)
p = PersonModel(name="Zaid", age=11, height=1.11, email="zaid@zaidalissa.me")
7 lines. Plus type checking, validation, and JSON export.
Conclusion
Stop fucking around, cunt. I mean writing simple data classes. Use dataclasses for internal structures. Use Pydantic for external boundaries. The choice is yours. If you control the input and the data, dataclasses give you clean code with minimal effort. One decorator eliminates the boring parts — equality checks, readable output, initialization, and you move on before you moved on. If you’re handling external data, user input, API calls, or config files, Pydantic’s validation saves you from runtime errors. Bad data gets caught at the boundary with clear error messages. You define validation rules once and Pydantic enforces them everywhere. The extra dependency is worth it.
Both approaches share a philosophy: data classes should be simple to write and easy to use. Python’s traditional approach in which you’re writing __init__, __repr__, and __eq__ by hand it wastes time and create bugs. Dataclasses and Pydantic eliminate wasting your time and invest in doing something better. Your code gets shorter. Your bugs get caught in a blink of an eye. Your debugging gets easier and funnier. And you spend less time writing boilerplate code and more time solving real problems, problems that can reshape human destiny. Pick the tool that fits your requirments. Use dataclasses inside your application. Use Pydantic at the frontier of interaction with external data. And stop writing classes the old way.
메타데이터
- post_id
- b809e4bf33f6
- slug
- pydantic-vs-dataclasses-what-i-actually-use-in-real-code-b809e4bf33f6
- url
- https://medium.com/@dataakkadian/pydantic-vs-dataclasses-what-i-actually-use-in-real-code-b809e4bf33f6
- canonical_url
- https://medium.com/@dataakkadian/pydantic-vs-dataclasses-what-i-actually-use-in-real-code-b809e4bf33f6
- author_url
- https://medium.com/@dataakkadian
- status
- ok
- fetched_at
- 2026-07-30 14:39:31