ASAmol Shukla
Projects
Courses
Prompts
Skills
Contact
Resume
Course Outline
Syllabus Overview

Complete Python Course: From Zero to Professional

Courses/Complete Python Course: From Zero to Professional/Lesson 19: Dataclasses & Modern OOP
40 mins lesson duration•8 mins read

Lesson 19: Dataclasses & Modern OOP

Write less boilerplate with @dataclass, frozen data, and professional OOP best practices.

Dataclasses — Boilerplate Be Gone

Writing a simple data class by hand means writing __init__, __repr__, and __eq__ — dozens of lines of boring code. The @dataclass decorator auto-generates all of it from your type-annotated fields:

from dataclasses import dataclass

@dataclass
class Course:
    title: str
    lessons: int
    tags: list = field(default_factory=list)

python = Course("Python", 25, ["python", "ai"])
print(python)              # Course(title='Python', lessons=25, tags=['python', 'ai'])
print(python == Course("Python", 25, ["python", "ai"]))   # True — free __eq__

Dataclasses (Python 3.7+) have become the default way to define data containers in modern Python. If a class is mostly "hold these fields and let me compare/print them", a dataclass is the professional choice.

What You'll Learn in This Lesson

  • Define dataclasses with type-annotated fields
  • Use field() and default_factory for mutable defaults
  • Freeze data with frozen=True
  • Add ordering with order=True

The Free Boilerplate

Without @dataclass With @dataclass
__init__ written by hand Auto-generated
__repr__ written by hand Auto-generated
__eq__ written by hand Auto-generated
Type hints optional Type hints required

What used to take 30 lines takes 10 — and is impossible to get wrong.

The "before" picture — what the decorator saves you from writing:

# Without dataclass — 15+ lines of ceremony
class Course:
    def __init__(self, title, lessons, tags):
        self.title = title
        self.lessons = lessons
        self.tags = tags

    def __repr__(self):
        return f"Course(title={self.title!r}, lessons={self.lessons!r}, tags={self.tags!r})"

    def __eq__(self, other):
        if not isinstance(other, Course):
            return NotImplemented
        return (self.title, self.lessons, self.tags) == (other.title, other.lessons, other.tags)

The @dataclass decorator generates exactly this — correctly, every time.


field() and default_factory — Safe Mutable Defaults

The classic Python trap is def f(items=[]) — a shared mutable default. Dataclasses solve it with field(default_factory=...):

from dataclasses import dataclass, field

@dataclass
class Student:
    name: str
    tags: list = field(default_factory=list)   # NEW list per instance

s1 = Student("Amol")
s1.tags.append("python")
print(s1.tags)                # ['python']
print(Student("Riya").tags)   # [] — separate list!

Never write tags: list = [] — every instance would share one list.

Why field(default_factory=...) and not just = []? The default value is evaluated once at class definition. default_factory is a function that runs per instance — each object gets its own fresh container.


Frozen Data — Immutable by Design

@dataclass(frozen=True) makes instances immutable — perfect for configuration and constants:

@dataclass(frozen=True)
class Config:
    version: str = "1.0"
    debug: bool = False

cfg = Config()
print(cfg.version)       # 1.0
# cfg.debug = True       # TypeError: frozen instance

Immutable data is safer: it cannot be accidentally changed, and instances can be hashed and used as dict keys.

Modifying a frozen dataclass — the sanctioned way:

from dataclasses import replace
cfg2 = replace(cfg, debug=True)   # returns a NEW Config with debug=True
print(cfg2)                       # Config(version='1.0', debug=True)
print(cfg.debug)                  # False — original untouched

order=True — Sorting for Free

@dataclass(order=True) adds __lt__, __le__, __gt__, __ge__ — your objects become sortable:

@dataclass(order=True)
class Product:
    price: float
    name: str

items = [Product(300, "Monitor"), Product(25, "Mouse")]
print(sorted(items))   # cheapest first

Field ordering matters with order=True: objects compare by fields left to right. Put the primary sort key first (price before name above).


Dataclasses vs NamedTuples vs Plain Classes

Tool When to use
NamedTuple Tiny immutable records, no methods
@dataclass Data containers with optional methods
Plain class Objects with significant behavior

Dataclasses with methods are fully supported — a dataclass can have regular methods alongside the auto-generated boilerplate:

@dataclass
class Course:
    title: str
    lessons: int

    def summary(self):          # a normal method
        return f"{self.title} — {self.lessons} lessons"

Real-World Dataclass Uses

  • DTOs (Data Transfer Objects): API request/response payloads with type hints.
  • Configuration objects: frozen settings loaded once.
  • Database models: table rows as typed records.
  • Value objects: coordinates, money, ranges with __eq__/order.
  • JSON serialization: combined with dataclasses.asdict() for quick json.dumps.

Common Mistakes to Avoid

  • Mistake: tags: list = [] as a default — Fix: field(default_factory=list).
  • Mistake: Expecting to modify a frozen dataclass — Fix: use dataclasses.replace(obj, field=value) for a modified copy.
  • Mistake: Using dataclasses for objects with complex behavior — Fix: use a regular class.
  • Mistake: Forgetting type annotations — Fix: dataclass fields need annotations: name: str.
  • Mistake: Relying on field order for order=True comparisons without checking which field sorts first — Fix: put the primary sort key first.

Professional Tips & Tricks

  • Use field(default_factory=list) for mutable defaults — never [] directly (shared trap).
  • Reach for dataclasses for DTOs, API payloads, and config objects.
  • Combine dataclasses with type hints for self-documenting code that linters can verify.
  • Use frozen=True for config and constants; replace() for safe "changes".
  • Use dataclasses.asdict() for quick conversion to plain dicts for JSON.

Key Takeaways

  • @dataclass auto-generates __init__, __repr__, and __eq__.
  • field(default_factory=list) gives each instance its own mutable default.
  • frozen=True makes immutable, hashable data.
  • order=True enables sorting.
  • Use dataclasses for data containers; plain classes for behavioral objects.
  • replace() modifies frozen instances by returning a new copy.

Next up: Module 6 — files, errors & professional Python.

Interactive Lesson Code Snippet
from dataclasses import dataclass, field

@dataclass
class Course:
    title: str
    lessons: int
    tags: list = field(default_factory=list)

    def summary(self):
        return f"{self.title} — {self.lessons} lessons"

@dataclass(frozen=True)
class Config:
    version: str = "1.0"
    debug: bool = False

# Free __init__, __repr__, __eq__
python = Course("Python", 25, ["python", "ai"])
print(python)
print(python.summary())
print("Equal:", python == Course("Python", 25, ["python", "ai"]))

cfg = Config()
print("Config version:", cfg.version)
# cfg.debug = True  # TypeError: frozen instance
Language: python

Lesson Code (Python)

from dataclasses import dataclass, field

@dataclass
class Course:
    title: str
    lessons: int
    tags: list = field(default_factory=list)

    def summary(self):
        return f"{self.title} — {self.lessons} lessons"

@dataclass(frozen=True)
class Config:
    version: str = "1.0"
    debug: bool = False

# Free __init__, __repr__, __eq__
python = Course("Python", 25, ["python", "ai"])
print(python)
print(python.summary())
print("Equal:", python == Course("Python", 25, ["python", "ai"]))

cfg = Config()
print("Config version:", cfg.version)
# cfg.debug = True  # TypeError: frozen instance

Console Output

Course(title='Python', lessons=25, tags=['python', 'ai'])
Python — 25 lessons
Equal: True
Config version: 1.0

Code Visualization Tips

  • 🧠Picture a dataclass as a form with labeled fields — the framework fills in the boring parts (init, repr, eq).
  • 🧠Visualize frozen=True as a sealed box: read anytime, change never.
  • 🧠default_factory makes a NEW list per instance — visualize each object getting its own empty box.

Professional Tips & Tricks

  • ⚡Use field(default_factory=list) for mutable defaults — never [] directly (shared trap).
  • ⚡Reach for dataclasses for DTOs, API payloads, and config objects.
  • ⚡Combine dataclasses with type hints for self-documenting code that linters can verify.

Python Code Judge & Practice Arena

LeetCode Style

Run real Python 3.12 WebAssembly code directly in your browser against automated test suites.

Solved:0 / 20
0 / 370 XP
Challenges:
Problem 1 of 20

Problem 1: Product Dataclass 1

Easy+10 XP
Define a dataclass `Product_1` with fields `id: int`, `name: str`, `price: float` with `order=True` so products sort by price ascending.
Sample Test Cases:
Input: [p.name for p in sorted([Product_1(1, 'Chair', 45.0), Product_1(2, 'Desk', 120.0), Product_1(3, 'Pen', 2.5)])]
Expected: ['Pen', 'Chair', 'Desk']
Input: Product_1(1, 'Book', 15.0).price
Expected: 15.0
main.pyPython 3.12 (WASM)
1
2
3
4
5
6
7
8
9
10
11
12
Press Run Code to test or Submit to verify test cases

Test Your Knowledge

Instant feedback

Quick Check: Lesson 19: Dataclasses, Field Configuration, Immutability & Validation

1 / 20
What does the `@dataclass` decorator generate automatically? from dataclasses import dataclass @dataclass class Product: name: str price: float

Up next · Continue learning

File Handling & Context Managers

Read and write files safely with the with statement, work with paths, and handle CSV data.

9 mins read45 mins
Start next lesson
Previous: Encapsulation, Properties & Magic MethodsNext: File Handling & Context Managers
Made withbyAmol Shukla·amolshukla.online
ASAmol Shukla

AI Developer, Trainer & Agentic AI Expert building practical learning systems and real-world AI applications.

Explore

  • Projects
  • Courses
  • Prompts
  • Skills
  • Contact
  • Experience
  • Blogs

Connect

  • Resume
  • Contact
© 2026 Amol Shukla·Created withbyamolshukla.online
Back to top