- What are dataclasses — a simpler way to create classes that store data
- Why use them — less code, more features, cleaner code
- How to use them — the @dataclass decorator
- Default values — setting default values for fields
- Useful features — __repr__, __eq__, ordering
- Real-world use — practical examples
What are Dataclasses?
A dataclass is a special type of class in Python that is designed to store data. It automatically adds useful methods to your class so you don't have to write them yourself.
Think of dataclasses like a filing cabinet with labeled drawers. Each drawer is a field, and you can quickly put things in and take things out without writing a lot of extra code.
Dataclasses were introduced in Python 3.7. They make it easy to create classes that are just containers for data.
💡 Key concept: Dataclasses are regular classes that automatically generate __init__, __repr__, __eq__, and other methods for you.
Why Use Dataclasses?
The Benefits of Dataclasses
Dataclasses save you from writing boring boilerplate code. Let's see the difference.
# Why Use Dataclasses?
print("=" * 50)
print("WHY USE DATACLASSES?")
print("=" * 50)
# ============================================================
# WITHOUT DATACLASSES — Lots of boilerplate
# ============================================================
print("\n❌ WITHOUT DATACLASSES:")
class PersonOld:
def __init__(self, name, age, city):
self.name = name
self.age = age
self.city = city
def __repr__(self):
return f"PersonOld(name={self.name!r}, age={self.age!r}, city={self.city!r})"
def __eq__(self, other):
if not isinstance(other, PersonOld):
return False
return (self.name == other.name and
self.age == other.age and
self.city == other.city)
# That's a LOT of code for a simple data container!
p1 = PersonOld("Alice", 30, "NYC")
p2 = PersonOld("Alice", 30, "NYC")
print(f" {p1}")
print(f" p1 == p2: {p1 == p2}")
print(" ❌ Too much code for a simple data class")
# ============================================================
# WITH DATACLASSES — Clean and Simple
# ============================================================
print("\n✅ WITH DATACLASSES:")
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
city: str
# That's it! All the methods are automatically generated
p1 = Person("Alice", 30, "NYC")
p2 = Person("Alice", 30, "NYC")
print(f" {p1}")
print(f" p1 == p2: {p1 == p2}")
print("\n✅ Benefits:")
print(" 1. Much less code")
print(" 2. Automatic __init__, __repr__, __eq__")
print(" 3. Easy to read and maintain")
print(" 4. Type hints built-in")
# ============================================================
# WHAT YOU GET FOR FREE
# ============================================================
print("\n" + "-" * 30)
print("WHAT DATACLASSES GIVE YOU")
print("-" * 30)
print("""
┌─────────────────────┬────────────────────────────────────────────┐
│ FEATURE │ WHAT IT DOES │
├─────────────────────┼────────────────────────────────────────────┤
│ __init__ │ Automatically creates the constructor │
│ __repr__ │ Nice string representation │
│ __eq__ │ Compare objects for equality │
│ __hash__ │ Makes objects usable in sets and dicts │
│ Ordering │ Can add sorting with order=True │
│ Immutability │ Can make fields read-only with frozen=True│
└─────────────────────┴────────────────────────────────────────────┘
📌 Dataclasses are regular classes with superpowers!
""")
Benefits of dataclasses:
- Less code — automatic __init__, __repr__, __eq__
- Cleaner code — focus on what matters
- Type hints built-in — better IDE support
- Easy to maintain — add fields easily
- More features — ordering, immutability
Quick Check: What methods does a dataclass automatically generate? (Answer: __init__, __repr__, __eq__, and optionally __hash__)
Basic Usage
Creating Your First Dataclass
Using dataclasses is very simple. Just add @dataclass above your class and define the fields with type hints.
# Basic Dataclass Usage
print("=" * 50)
print("BASIC DATACLASS USAGE")
print("=" * 50)
from dataclasses import dataclass
# ============================================================
# SIMPLE DATACLASS
# ============================================================
print("\n1. SIMPLE DATACLASS")
@dataclass
class Book:
title: str
author: str
pages: int
# Creating objects
book1 = Book("Python Programming", "Alice Smith", 300)
book2 = Book("Data Science", "Bob Jones", 250)
print(f" Book 1: {book1}")
print(f" Book 2: {book2}")
print(f" Book 1 pages: {book1.pages}")
print(f" Book 1 title: {book1.title}")
# Access and modify fields
book1.pages = 320
print(f" After update: {book1}")
# ============================================================
# DATACLASS WITH DIFFERENT TYPES
# ============================================================
print("\n2. DATACLASS WITH DIFFERENT TYPES")
@dataclass
class Employee:
name: str
age: int
salary: float
is_full_time: bool
department: str = "General" # Default value
emp = Employee("Alice", 30, 75000.50, True)
emp2 = Employee("Bob", 25, 60000.00, True, "Engineering")
print(f" Employee 1: {emp}")
print(f" Employee 2: {emp2}")
# ============================================================
# COMPARING OBJECTS
# ============================================================
print("\n3. COMPARING OBJECTS")
@dataclass
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)
print(f" p1: {p1}")
print(f" p2: {p2}")
print(f" p3: {p3}")
print(f" p1 == p2: {p1 == p2}")
print(f" p1 == p3: {p1 == p3}")
print(f" p1 is p2: {p1 is p2}") # Different objects, so False
# ============================================================
# WORKING WITH LISTS OF DATACLASSES
# ============================================================
print("\n4. WORKING WITH LISTS")
@dataclass
class Product:
name: str
price: float
products = [
Product("Laptop", 999.99),
Product("Phone", 699.99),
Product("Headphones", 149.99)
]
for product in products:
print(f" {product.name}: ${product.price}")
# Find a product
laptop = Product("Laptop", 999.99)
print(f" Is Laptop in list? {laptop in products}") # True
print(f" Index of Laptop: {products.index(laptop)}") # 0
# ============================================================
# DATACLASS WITH METHODS
# ============================================================
print("\n5. ADDING METHODS TO DATACLASSES")
@dataclass
class Rectangle:
width: float
height: float
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
rect = Rectangle(5, 3)
print(f" Rectangle: {rect}")
print(f" Area: {rect.area()}")
print(f" Perimeter: {rect.perimeter()}")
Basic usage key points:
- @dataclass — the decorator that makes it work
- Type hints — required for each field
- Default values — can be set like regular classes
- Methods — you can add your own methods
- Comparison — equality works automatically
Quick Check: What do you need to add above a class to make it a dataclass? (Answer: @dataclass)
Default Values
Setting Default Values
You can set default values for fields. This is useful when you want some fields to be optional.
# Default Values in Dataclasses
print("=" * 50)
print("DEFAULT VALUES")
print("=" * 50)
from dataclasses import dataclass
from datetime import datetime
# ============================================================
# BASIC DEFAULT VALUES
# ============================================================
print("\n1. BASIC DEFAULT VALUES")
@dataclass
class User:
username: str
email: str
is_active: bool = True # Default value
age: int = 0
city: str = "Unknown"
# Create with defaults
user1 = User("alice", "alice@example.com")
user2 = User("bob", "bob@example.com", is_active=False)
user3 = User("charlie", "charlie@example.com", 25, "NYC")
print(f" User 1: {user1}")
print(f" User 2: {user2}")
print(f" User 3: {user3}")
# ============================================================
# IMPORTANT: DEFAULT VALUES ORDER
# ============================================================
print("\n2. IMPORTANT: DEFAULT VALUES ORDER")
# ❌ BAD: Fields without defaults after fields with defaults
# @dataclass
# class Bad:
# name: str = "Default" # Has default
# age: int # No default — ERROR!
# ✅ GOOD: Fields without defaults first
@dataclass
class Good:
name: str # No default first
age: int # No default
city: str = "Unknown" # Default after
print(" ✅ Fields without defaults must come before fields with defaults")
# ============================================================
# USING DEFAULT FACTORIES FOR MUTABLE DEFAULTS
# ============================================================
print("\n3. DEFAULT FACTORIES FOR MUTABLE VALUES")
from dataclasses import field
@dataclass
class Team:
name: str
members: list = field(default_factory=list) # New list for each instance
scores: dict = field(default_factory=dict)
created_at: datetime = field(default_factory=datetime.now)
team1 = Team("Developers")
team1.members.append("Alice")
team1.members.append("Bob")
team2 = Team("Designers")
team2.members.append("Charlie")
print(f" Team 1: {team1}")
print(f" Team 2: {team2}")
print(f" Team 1 members: {team1.members}")
print(f" Team 2 members: {team2.members}")
# ❌ BAD: Don't use mutable defaults directly
# @dataclass
# class Bad:
# items: list = [] # This is WRONG!
# # All instances will share the same list!
# ============================================================
# DEFAULT FACTORIES WITH LAMBDA
# ============================================================
print("\n4. DEFAULT FACTORIES WITH LAMBDA")
@dataclass
class Task:
title: str
priority: int = 1
tags: list = field(default_factory=lambda: ["general"])
subtasks: list = field(default_factory=lambda: [])
task1 = Task("Write code")
task2 = Task("Review code", priority=2)
task1.tags.append("python")
task2.tags.append("review")
print(f" Task 1: {task1}")
print(f" Task 2: {task2}")
# ============================================================
# DEFAULT VALUES SUMMARY
# ============================================================
print("\n" + "-" * 30)
print("DEFAULT VALUES SUMMARY")
print("-" * 30)
print("""
┌─────────────────────────────┬────────────────────────────────────────────┐
│ TYPE │ HOW TO DO IT │
├─────────────────────────────┼────────────────────────────────────────────┤
│ Simple default │ age: int = 0 │
│ │ │
│ Mutable default (list) │ items: list = field(default_factory=list) │
│ │ │
│ Mutable default (dict) │ scores: dict = field(default_factory=dict)│
│ │ │
│ Default from function │ created: datetime = field( │
│ │ default_factory=datetime.now) │
│ │ │
│ Lambda default │ tags: list = field( │
│ │ default_factory=lambda: ["general"]) │
└─────────────────────────────┴────────────────────────────────────────────┘
📌 Always use default_factory for mutable default values!
""")
Default values key points:
- Simple defaults —
age: int = 0 - Order matters — fields without defaults must come first
- Mutable defaults — use
field(default_factory=list) - No mutable defaults directly —
items: list = []is wrong - Default factory — creates a new value for each instance
Quick Check: How do you set a default value for a list in a dataclass? (Answer: items: list = field(default_factory=list))
Useful Features
Features That Make Life Easier
Dataclasses come with several features that make them even more useful.
# Useful Features of Dataclasses
print("=" * 50)
print("USEFUL FEATURES")
print("=" * 50)
from dataclasses import dataclass, field, asdict, astuple
# ============================================================
# 1. CONVERTING TO DICT OR TUPLE
# ============================================================
print("\n1. CONVERTING TO DICT OR TUPLE")
@dataclass
class Employee:
name: str
role: str
salary: int
emp = Employee("Alice", "Developer", 80000)
# Convert to dictionary
emp_dict = asdict(emp)
print(f" As dict: {emp_dict}")
# Convert to tuple
emp_tuple = astuple(emp)
print(f" As tuple: {emp_tuple}")
# Useful for JSON serialization
import json
json_str = json.dumps(asdict(emp))
print(f" As JSON: {json_str}")
# ============================================================
# 2. ORDERING (sorting)
# ============================================================
print("\n2. ORDERING (SORTING)")
@dataclass(order=True)
class Person:
name: str
age: int
people = [
Person("Charlie", 35),
Person("Alice", 30),
Person("Bob", 25)
]
print(f" Original: {people}")
# Sort by age (default)
sorted_by_age = sorted(people)
print(f" Sorted by age: {sorted_by_age}")
# Sort by name
sorted_by_name = sorted(people, key=lambda p: p.name)
print(f" Sorted by name: {sorted_by_name}")
# ============================================================
# 3. IMMUTABLE DATACLASSES (frozen)
# ============================================================
print("\n3. IMMUTABLE DATACLASSES")
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
print(f" Point: {p}")
print(f" x: {p.x}, y: {p.y}")
# Can't modify fields
try:
p.x = 5
except AttributeError as e:
print(f" ❌ Can't modify: {e}")
# But can create new objects
p2 = Point(3, 4)
print(f" New point: {p2}")
# ============================================================
# 4. SLOTS (memory efficient)
# ============================================================
print("\n4. SLOTS (MEMORY EFFICIENT)")
@dataclass(slots=True)
class Product:
name: str
price: float
# Uses less memory than regular classes
p = Product("Laptop", 999.99)
print(f" Product: {p}")
# Can add new attributes (slots prevents this)
try:
p.discount = 10
except AttributeError as e:
print(f" ❌ Can't add new attribute: {e}")
# ============================================================
# 5. POST-INIT PROCESSING
# ============================================================
print("\n5. POST-INIT PROCESSING")
@dataclass
class PersonWithValidation:
name: str
age: int
def __post_init__(self):
"""Called after __init__ for validation or processing"""
if self.age < 0:
raise ValueError("Age cannot be negative")
if not self.name.strip():
raise ValueError("Name cannot be empty")
# Can also transform data
self.name = self.name.title()
try:
p1 = PersonWithValidation("alice", 30)
print(f" Valid person: {p1} (name was capitalized)")
except ValueError as e:
print(f" Error: {e}")
try:
p2 = PersonWithValidation("", 25)
except ValueError as e:
print(f" Error: {e}")
try:
p3 = PersonWithValidation("Bob", -5)
except ValueError as e:
print(f" Error: {e}")
# ============================================================
# 6. SUMMARY
# ============================================================
print("\n" + "-" * 30)
print("FEATURES SUMMARY")
print("-" * 30)
print("""
┌─────────────────────┬─────────────────────────────────────────────────────┐
│ FEATURE │ WHAT IT DOES │
├─────────────────────┼─────────────────────────────────────────────────────┤
│ asdict() │ Convert dataclass to dictionary │
│ astuple() │ Convert dataclass to tuple │
│ order=True │ Makes objects sortable │
│ frozen=True │ Makes objects immutable │
│ slots=True │ Saves memory, prevents new attributes │
│ __post_init__ │ Validation and processing after initialization │
└─────────────────────┴─────────────────────────────────────────────────────┘
""")
Useful features key points:
- asdict() — convert to dictionary
- astuple() — convert to tuple
- order=True — enables sorting
- frozen=True — makes immutable
- slots=True — saves memory
- __post_init__ — validation and processing
Quick Check: How do you make a dataclass immutable? (Answer: Add frozen=True to the dataclass decorator)
Advanced Features
More Advanced Options
# Advanced Dataclass Features
print("=" * 50)
print("ADVANCED FEATURES")
print("=" * 50)
from dataclasses import dataclass, field
from typing import Optional, List
# ============================================================
# 1. FIELD WITH METADATA
# ============================================================
print("\n1. FIELD WITH METADATA")
@dataclass
class Person:
name: str
age: int = field(default=0, metadata={"min": 0, "max": 150})
email: str = field(default="", metadata={"pattern": r".+@.+"})
tags: List[str] = field(default_factory=list, metadata={"description": "Tags for the person"})
# Access metadata
print(f" Person fields:")
for f in Person.__dataclass_fields__.items():
print(f" {f[0]}: metadata = {f[1].metadata}")
# ============================================================
# 2. INHERITANCE
# ============================================================
print("\n2. INHERITANCE WITH DATACLASSES")
@dataclass
class Animal:
name: str
species: str
@dataclass
class Dog(Animal):
breed: str
age: int
@dataclass
class Cat(Animal):
color: str
is_indoor: bool = True
dog = Dog("Rex", "Canine", "German Shepherd", 3)
cat = Cat("Whiskers", "Feline", "Orange")
print(f" Dog: {dog}")
print(f" Cat: {cat}")
# Inheritance works naturally
print(f" Dog name: {dog.name}")
print(f" Cat species: {cat.species}")
# ============================================================
# 3. OPTIONAL FIELDS
# ============================================================
print("\n3. OPTIONAL FIELDS")
from typing import Optional
@dataclass
class Student:
name: str
age: int
email: Optional[str] = None # Can be None or a string
grade: Optional[int] = None
s1 = Student("Alice", 20, "alice@example.com", 85)
s2 = Student("Bob", 22) # Uses defaults
print(f" Student 1: {s1}")
print(f" Student 2: {s2}")
# ============================================================
# 4. NESTED DATACLASSES
# ============================================================
print("\n4. NESTED DATACLASSES")
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class UserWithAddress:
username: str
email: str
address: Address
# Create nested objects
address = Address("123 Main St", "Boston", "02101")
user = UserWithAddress("alice", "alice@example.com", address)
print(f" User: {user}")
print(f" User's city: {user.address.city}")
# Convert nested to dict
user_dict = asdict(user)
print(f" As dict: {user_dict}")
# ============================================================
# 5. CLASS VARIABLES
# ============================================================
print("\n5. CLASS VARIABLES")
@dataclass
class Car:
# Class variable (shared by all instances)
wheels: int = 4
# Instance variables
make: str
model: str
year: int
car1 = Car("Toyota", "Camry", 2023)
car2 = Car("Honda", "Civic", 2022)
print(f" Car 1: {car1}")
print(f" Car 2: {car2}")
print(f" Wheels (class): {Car.wheels}")
print(f" Wheels (instance): {car1.wheels}")
# ============================================================
# 6. SUMMARY
# ============================================================
print("\n" + "-" * 30)
print("ADVANCED FEATURES SUMMARY")
print("-" * 30)
print("""
┌─────────────────────┬─────────────────────────────────────────────────────┐
│ FEATURE │ WHAT IT DOES │
├─────────────────────┼─────────────────────────────────────────────────────┤
│ Field metadata │ Add extra information to fields │
│ Inheritance │ Dataclasses can inherit from other dataclasses │
│ Optional fields │ Use Optional[type] for nullable fields │
│ Nested dataclasses │ Use dataclasses inside other dataclasses │
│ Class variables │ Variables shared by all instances │
└─────────────────────┴─────────────────────────────────────────────────────┘
""")
Advanced features key points:
- Field metadata — add extra info to fields
- Inheritance — works naturally with dataclasses
- Optional — use
Optional[type]for nullable fields - Nested — dataclasses can contain other dataclasses
- Class variables — shared across all instances
Quick Check: Can dataclasses inherit from other dataclasses? (Answer: Yes, inheritance works naturally with dataclasses)
Real-World Example
Building an E-commerce System
# Real-World Example: E-commerce System
from dataclasses import dataclass, field, asdict
from typing import Optional, List
from datetime import datetime
import json
print("=" * 60)
print("E-COMMERCE SYSTEM WITH DATACLASSES")
print("=" * 60)
# ============================================================
# DATA MODELS
# ============================================================
@dataclass
class Product:
id: int
name: str
price: float
category: str
in_stock: bool = True
tags: List[str] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.now)
def __post_init__(self):
"""Validate product data"""
if self.price < 0:
raise ValueError("Price cannot be negative")
if not self.name.strip():
raise ValueError("Product name cannot be empty")
@dataclass
class Customer:
id: int
name: str
email: str
is_active: bool = True
created_at: datetime = field(default_factory=datetime.now)
orders: List['Order'] = field(default_factory=list)
def __post_init__(self):
if not self.email.strip():
raise ValueError("Email cannot be empty")
if '@' not in self.email:
raise ValueError("Invalid email format")
@dataclass
class OrderItem:
product: Product
quantity: int
@property
def subtotal(self) -> float:
return self.product.price * self.quantity
def __post_init__(self):
if self.quantity <= 0:
raise ValueError("Quantity must be positive")
if not self.product.in_stock:
raise ValueError(f"Product {self.product.name} is out of stock")
@dataclass
class Order:
id: int
customer: Customer
items: List[OrderItem] = field(default_factory=list)
status: str = "pending"
created_at: datetime = field(default_factory=datetime.now)
shipped_at: Optional[datetime] = None
@property
def total(self) -> float:
return sum(item.subtotal for item in self.items)
@property
def item_count(self) -> int:
return len(self.items)
def add_item(self, product: Product, quantity: int) -> None:
"""Add an item to the order"""
item = OrderItem(product, quantity)
self.items.append(item)
def ship(self) -> None:
"""Mark order as shipped"""
if self.status == "pending":
self.status = "shipped"
self.shipped_at = datetime.now()
else:
raise ValueError(f"Cannot ship order with status: {self.status}")
# ============================================================
# CREATE SAMPLE DATA
# ============================================================
print("\n1. CREATING PRODUCTS")
products = [
Product(1, "Laptop", 999.99, "Electronics"),
Product(2, "Phone", 699.99, "Electronics", tags=["smartphone"]),
Product(3, "Book", 29.99, "Books", tags=["python", "programming"]),
Product(4, "Headphones", 149.99, "Electronics", in_stock=False)
]
for product in products:
print(f" {product.id}: {product.name} (${product.price})")
print("\n2. CREATING CUSTOMERS")
customer1 = Customer(1, "Alice", "alice@example.com")
customer2 = Customer(2, "Bob", "bob@example.com")
print(f" Customer 1: {customer1}")
print(f" Customer 2: {customer2}")
print("\n3. CREATING ORDERS")
order1 = Order(1, customer1)
order1.add_item(products[0], 1) # 1 Laptop
order1.add_item(products[2], 2) # 2 Books
order2 = Order(2, customer2)
order2.add_item(products[1], 2) # 2 Phones
order2.add_item(products[3], 1) # 1 Headphones (out of stock!)
print(f" Order 1: {order1} (Total: ${order1.total:.2f})")
for item in order1.items:
print(f" {item.product.name} x{item.quantity} = ${item.subtotal:.2f}")
print("\n4. SHIPPING ORDERS")
try:
order1.ship()
print(f" Order 1 shipped at: {order1.shipped_at}")
except ValueError as e:
print(f" Error: {e}")
print("\n5. ORDER SUMMARY")
print(f" Customer: {order1.customer.name}")
print(f" Items: {order1.item_count}")
print(f" Total: ${order1.total:.2f}")
print(f" Status: {order1.status}")
print("\n6. CONVERTING TO JSON")
order_dict = asdict(order1)
print(f" Order as dict:")
for key, value in order_dict.items():
if key != "items":
print(f" {key}: {value}")
else:
print(f" items: {len(value)} items")
print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print("✅ Dataclasses make data models clean and simple")
print("✅ Validation is easy with __post_init__")
print("✅ Computed properties work with @property")
print("✅ Converting to dict/JSON is built-in")
print("✅ Nested dataclasses handle complex data")
print("✅ Immutability with frozen=True is great for data")
Real-world example key points:
- Clean models — Product, Customer, Order are simple and clear
- Validation — __post_init__ ensures data quality
- Computed properties — total, item_count, subtotal
- Nested structure — Order contains OrderItems
- JSON ready — asdict() converts to dictionary
Quick Check: What method is called after __init__ in a dataclass? (Answer: __post_init__)
Best Practices
Using Dataclasses Effectively
# Best Practices for Dataclasses
print("=" * 60)
print("BEST PRACTICES FOR DATACLASSES")
print("=" * 60)
from dataclasses import dataclass, field
from typing import Optional, List
# ============================================================
# 1. USE TYPE HINTS
# ============================================================
print("\n1. USE TYPE HINTS")
# ✅ GOOD: Always use type hints
@dataclass
class GoodPerson:
name: str
age: int
email: str
# ❌ BAD: Missing type hints
# @dataclass
# class BadPerson:
# name # Missing type hint
# age # Missing type hint
print(" ✅ Type hints make dataclasses work")
# ============================================================
# 2. USE FIELD FOR MUTABLE DEFAULTS
# ============================================================
print("\n2. USE FIELD FOR MUTABLE DEFAULTS")
# ✅ GOOD: Using field for lists
@dataclass
class Team:
name: str
members: List[str] = field(default_factory=list)
# ❌ BAD: Direct mutable default
# @dataclass
# class BadTeam:
# name: str
# members: List[str] = [] # WRONG!
print(" ✅ Use default_factory for mutable values")
# ============================================================
# 3. USE FROZEN FOR IMMUTABLE DATA
# ============================================================
print("\n3. USE FROZEN FOR IMMUTABLE DATA")
# ✅ GOOD: Frozen for data that shouldn't change
@dataclass(frozen=True)
class Point:
x: int
y: int
# ❌ BAD: Mutable when it should be immutable
@dataclass
class MutablePoint:
x: int
y: int
print(" ✅ Use frozen=True for immutable data")
# ============================================================
# 4. USE __post_init__ FOR VALIDATION
# ============================================================
print("\n4. USE __post_init__ FOR VALIDATION")
@dataclass
class Product:
name: str
price: float
def __post_init__(self):
if self.price < 0:
raise ValueError("Price cannot be negative")
if not self.name:
raise ValueError("Name cannot be empty")
print(" ✅ Validate data in __post_init__")
# ============================================================
# 5. USE SLOTS FOR MEMORY EFFICIENCY
# ============================================================
print("\n5. USE SLOTS FOR MEMORY EFFICIENCY")
@dataclass(slots=True)
class EfficientUser:
name: str
age: int
print(" ✅ Use slots=True for many instances")
# ============================================================
# 6. DON'T OVERUSE DATACLASSES
# ============================================================
print("\n6. DON'T OVERUSE DATACLASSES")
# ✅ GOOD: Use dataclasses for data containers
@dataclass
class UserData:
name: str
email: str
# ❌ BAD: Using dataclass for business logic
@dataclass
class UserService: # This should be a regular class
def process_user(self):
pass
print(" ✅ Use dataclasses for data, classes for logic")
# ============================================================
# 7. SUMMARY
# ============================================================
print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
┌─────────────────────────────┬─────────────────────────────────────────────┐
│ PRACTICE │ WHY IT MATTERS │
├─────────────────────────────┼─────────────────────────────────────────────┤
│ Always use type hints │ Required for dataclasses │
│ │ │
│ Use field for mutable │ Prevents sharing between instances │
│ defaults │ │
│ │ │
│ Use frozen for immutable │ Data safety and hashable objects │
│ data │ │
│ │ │
│ Validate in __post_init__ │ Ensure data quality │
│ │ │
│ Use slots for many objects │ Memory efficiency │
│ │ │
│ Use dataclasses for data │ Keep responsibilities clear │
└─────────────────────────────┴─────────────────────────────────────────────┘
📌 Dataclasses are for data, not business logic!
""")
Best practices summary:
- Use type hints — required for dataclasses
- Use field for mutable defaults — prevents sharing
- Use frozen for immutable data — data safety
- Validate in __post_init__ — ensure data quality
- Use slots for many objects — memory efficiency
- Use dataclasses for data — not business logic
Quick Check: What should you use dataclasses for? (Answer: Data containers, not business logic)
Try It Yourself
Experiment with dataclasses in the editor below.
DATACLASSES - PRACTICE
==================================================
1. BASIC DATACLASS
Student 1: Student(name='Alice', age=20, grade='A', active=True)
Student 2: Student(name='Bob', age=22, grade='B', active=False)
s1 == s2: False
2. DATACLASS WITH METHODS
Circle: radius=5
Area: 78.54
Circumference: 31.42
3. DATACLASS WITH DEFAULT FACTORY
Playlist: Playlist(name='My Favorites', songs=['Song 1', 'Song 2', 'Song 3'])
4. FROZEN DATACLASS
Coordinates: Coordinates(latitude=40.7128, longitude=-74.006)
❌ Cannot modify: cannot assign to field 'latitude'
5. CONVERT TO DICT
Book: Book(title='Python Programming', author='Alice Smith', year=2023)
As dict: {'title': 'Python Programming', 'author': 'Alice Smith', 'year': 2023}
You've Got It!
You now understand dataclasses in Python. You know how to create them, use default values, add methods, and use advanced features like frozen and slots.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What are dataclasses in Python?
When should I use a dataclass instead of a regular class?
Do dataclasses work in older Python versions?
dataclasses available on PyPI. Python 3.6 and below don't support dataclasses natively.
Can I add methods to a dataclass?
What's the difference between dataclasses and namedtuples?
Can dataclasses be used with JSON?
asdict() to convert a dataclass to a dictionary, then use json.dumps() to convert to JSON. For converting back, you can create a dataclass from a dictionary.
Where to Go From Here
Now that you understand dataclasses in Python, check out these related topics:
Type Hints
Learn more about type hints that dataclasses use.
Learn More →Property Decorator
Learn how to use @property with dataclasses.
Learn More →Context Managers
Learn how context managers work with dataclasses.
Learn More →