Lesson 18 · Python standard library deep dive
Python dataclasses Explained: Cleaner Classes, Less Code | Standard Library #18
Video eighteen of the twenty-five-part series: dataclasses, for cutting way down on class boilerplate. Auto-generated init/repr/eq, defaults, frozen…
- CoursePython standard library deep dive
- Lesson18 of 24
- Video18 min
- FormatJupyter notebook · 11 code cells
What you'll learn
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbPython Standard Library Deep-Dive, Video 18: dataclasses#
- Video eighteen of the twenty-five-part series: dataclasses, for cutting way down on class boilerplate.
- Auto-generated init/repr/eq, defaults, frozen immutability, ordering, validation, and inheritance.
- Let's get into it.
Part 1: The Boilerplate Problem#
class PlainPoint:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'PlainPoint(x={self.x}, y={self.y})'
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
p1 = PlainPoint(1, 2)
p2 = PlainPoint(1, 2)
print(p1)
print(p1 == p2)
Part 2: @dataclass Basics#
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1)
print(p1 == p2)
print(p1 is p2)
Part 3: Default Values and default_factory#
from dataclasses import field
@dataclass
class Task:
title: str
done: bool = False
tags: list = field(default_factory=list)
t1 = Task('write lesson')
t2 = Task('review lesson')
t1.tags.append('urgent')
print(t1.tags)
print(t2.tags)
Part 4: Type Hints Are Not Enforced at Runtime#
@dataclass
class Item:
name: str
price: float
wrong_types = Item(name=123, price='not a number')
print(wrong_types)
print(type(wrong_types.name))
Part 5: frozen=True - Immutability#
@dataclass(frozen=True)
class FrozenPoint:
x: int
y: int
fp = FrozenPoint(3, 4)
print(fp)
try:
fp.x = 99
except Exception as e:
print(f'{type(e).__name__}: {e}')
points = {FrozenPoint(0, 0): 'origin', FrozenPoint(3, 4): 'other'}
print(points[FrozenPoint(0, 0)])
Part 6: order=True - Comparison and Sorting#
@dataclass(order=True)
class Version:
major: int
minor: int
patch: int
versions = [Version(1, 2, 0), Version(1, 0, 5), Version(2, 0, 0), Version(1, 2, 3)]
print(sorted(versions))
print(Version(1, 0, 0) < Version(1, 0, 1))
Part 7: post_init - Validation and Derived Fields#
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False)
def __post_init__(self):
if self.width <= 0 or self.height <= 0:
raise ValueError('width and height must be positive')
self.area = self.width * self.height
r = Rectangle(4, 5)
print(r)
try:
Rectangle(-1, 5)
except ValueError as e:
print(f'caught: {e}')
Part 8: field() Options - repr, compare, and metadata#
@dataclass
class User:
username: str
password_hash: str = field(repr=False)
login_count: int = field(default=0, compare=False)
u1 = User('ana', 'abc123hash')
u2 = User('ana', 'abc123hash')
u2.login_count = 50
print(u1)
print(u1 == u2)
Part 9: Inheritance with dataclasses#
@dataclass
class Animal:
name: str
species: str
@dataclass
class Pet(Animal):
owner: str
nickname: str = 'buddy'
p = Pet(name='Rex', species='dog', owner='Ana')
print(p)
Part 10: asdict/astuple and a Real Config Pattern#
from dataclasses import asdict, astuple
@dataclass
class AppConfig:
debug: bool = False
max_connections: int = 10
allowed_hosts: list = field(default_factory=lambda: ['localhost'])
def __post_init__(self):
if self.max_connections <= 0:
raise ValueError('max_connections must be positive')
config = AppConfig(debug=True, max_connections=50)
print(config)
print(asdict(config))
print(astuple(config))
Wrap-Up: What You Learned#
- @dataclass auto-generates init, repr, and eq from type-annotated class fields.
- Plain defaults work like normal; mutable defaults need field(default_factory=...) to avoid sharing across instances.
- Type annotations are documentation only; Python never enforces them at runtime.
- frozen=True makes fields read-only and makes instances hashable, usable as dict keys.
- order=True generates comparison methods, comparing fields in declared order like tuples.
- post_init runs right after generated init, ideal for validation or computing derived fields.
- field() also supports repr=False, compare=False, and metadata for fine-grained control.
- dataclasses support inheritance; required fields must still precede defaulted ones across the full set.
- asdict/astuple convert an instance, recursively, into a plain dict or tuple.
- A real pattern: a validated, typed application config object built once at startup.
- That wraps up dataclasses. Next up: typing and enum, for type hints and enumerations.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



