Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • 📝 Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • 📝 Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • 📝 Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • 📝 Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • 📝 While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • 📝 For Each Assignments
    • 📝 All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • 📝 Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • 📝 Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • 📝 Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • 📝 Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • 📝 Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • 📝 Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • 📝 Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • 📝 Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • 📝 Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • 📝 Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • 📝 Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • 📝 Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • 📝 Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • 📝 Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • 📝 Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • 📝 Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • 📝 Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • 📝 Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • 📝 Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Dataclasses

Python Dataclasses - Complete Guide

Write less code and do more with dataclasses.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
On this page:
  • What are Dataclasses?
  • Why Use Dataclasses?
  • Basic Usage
  • Default Values
  • Useful Features
  • Advanced Features
  • Real-World Example
  • Best Practices
  • Try It Yourself
  • Quick Quiz
  • Frequently Asked Questions
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • 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?

1

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

2

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

3

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

4

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

5

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

6

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

7

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.

Loading Pyodide... 0%
Python Code Editor
==================================================
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:

1. What decorator is used to create a dataclass?
2. What methods does a dataclass automatically provide?
3. How do you set a default value for a list in a dataclass?
4. How do you make a dataclass immutable?
5. What method is called after __init__ in a dataclass?

Frequently Asked Questions

What are dataclasses in Python? ▼

Dataclasses are a special type of class that automatically generates common methods like __init__, __repr__, and __eq__. They're designed for storing data and reduce the amount of boilerplate code you need to write.

When should I use a dataclass instead of a regular class? ▼

Use dataclasses when you need a class that primarily stores data. They're perfect for data models, configuration objects, and transfer objects. Use regular classes when you have complex behavior and business logic.

Do dataclasses work in older Python versions? ▼

Dataclasses were introduced in Python 3.7. For Python 3.6, you can use the backport package dataclasses available on PyPI. Python 3.6 and below don't support dataclasses natively.

Can I add methods to a dataclass? ▼

Yes! Dataclasses are regular classes, so you can add any methods you want. You can add helper methods, computed properties with @property, and validation methods.

What's the difference between dataclasses and namedtuples? ▼

Dataclasses are more flexible and powerful. They support mutable fields, inheritance, default values, type hints, and methods. Namedtuples are immutable and more memory efficient but less flexible.

Can dataclasses be used with JSON? ▼

Yes! You can use 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 →
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
  • Python Lists and Dictionaries Interview Questions
  • Python OOP Interview Questions
  • SQL Set Operators Interview Questions
Previous: Async/Await Next: Context Managers →