Skip to content
Codeloom
Python

Python Dataclasses: A Complete Guide

Master Python dataclasses — automatic __init__, __repr__, ordering, immutability, default factories, and post-init processing.

·3 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • How dataclasses reduce boilerplate for data-holding classes
  • Field options: defaults, factories, metadata
  • Frozen dataclasses for immutability
  • Post-init processing and inheritance

Prerequisites

  • Python classes and type hints

Dataclasses, introduced in Python 3.7, generate __init__, __repr__, __eq__, and more from class annotations. They eliminate boilerplate for classes that primarily hold data.

Before vs after

Without dataclasses:

class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

    def __eq__(self, other):
        return isinstance(other, Point) and self.x == other.x and self.y == other.y

With dataclasses:

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

You get __init__, __repr__, and __eq__ automatically.

p = Point(3.0, 4.0)
print(p)        # Point(x=3.0, y=4.0)
print(p == Point(3.0, 4.0))  # True

Default values

@dataclass
class Config:
    host: str = "localhost"
    port: int = 8080
    debug: bool = False

config = Config()
print(config)  # Config(host='localhost', port=8080, debug=False)

Fields with defaults must come after fields without defaults.

Default factories

For mutable defaults (lists, dicts), use field(default_factory=...):

from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    tags: list[str] = field(default_factory=list)
    metadata: dict = field(default_factory=dict)

u1 = User("Alice")
u2 = User("Bob")
u1.tags.append("admin")
print(u2.tags)  # [] — separate list, not shared

Frozen (immutable) dataclasses

@dataclass(frozen=True)
class Coordinate:
    lat: float
    lon: float

c = Coordinate(40.7128, -74.0060)
# c.lat = 0  # FrozenInstanceError

Frozen dataclasses are hashable, so they can be used as dict keys or set members.

Ordering

@dataclass(order=True)
class Version:
    major: int
    minor: int
    patch: int

versions = [Version(2, 0, 0), Version(1, 5, 3), Version(1, 5, 2)]
print(sorted(versions))
# [Version(major=1, minor=5, patch=2), Version(major=1, minor=5, patch=3), Version(major=2, minor=0, patch=0)]

Post-init processing

__post_init__ runs after the generated __init__.

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)

    def __post_init__(self):
        self.area = self.width * self.height

r = Rectangle(3, 4)
print(r.area)  # 12.0

Field options

@dataclass
class Product:
    name: str
    price: float
    _id: str = field(repr=False)         # hidden from repr
    _cache: dict = field(init=False,     # not in __init__
                         repr=False,      # not in repr
                         compare=False,   # not in __eq__
                         default_factory=dict)

Inheritance

@dataclass
class Animal:
    name: str
    species: str

@dataclass
class Pet(Animal):
    owner: str
    vaccinated: bool = True

pet = Pet(name="Rex", species="Dog", owner="Alice")
print(pet)  # Pet(name='Rex', species='Dog', owner='Alice', vaccinated=True)

Converting to dicts and tuples

from dataclasses import asdict, astuple

@dataclass
class User:
    name: str
    age: int
    email: str

user = User("Alice", 30, "alice@example.com")
print(asdict(user))   # {'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
print(astuple(user))  # ('Alice', 30, 'alice@example.com')

Slots

Python 3.10+ supports slots=True for memory efficiency and faster attribute access.

@dataclass(slots=True)
class Point:
    x: float
    y: float

Dataclass vs NamedTuple vs Pydantic

FeaturedataclassNamedTuplePydantic
Mutable by defaultYesNoYes
Type validation at runtimeNoNoYes
JSON serializationManualManualBuilt-in
Default valuesYesYesYes
InheritanceYesLimitedYes
PerformanceFastFastestSlower (validation)

Use dataclasses for internal data structures, NamedTuple for immutable lightweight records, and Pydantic for API boundaries where validation matters.

Summary

Dataclasses eliminate boilerplate for data-holding classes. Use frozen=True for immutability, field(default_factory=...) for mutable defaults, __post_init__ for computed fields, and slots=True for performance. They are the standard way to define structured data in modern Python.