Dataclasses

Difficulty: Intermediate

Question

What are Python dataclasses? How do they compare to regular classes and namedtuples?

Answer

@dataclass (Python 3.7+) automatically generates __init__, __repr__, __eq__, and optionally __lt__, __hash__ based on class attributes.

Advantages over regular class: - No boilerplate __init__ with self.x = x - Auto-generated __repr__ and __eq__ - Clean, declarative syntax with type annotations

Key parameters: - frozen=True: makes instances immutable (generates __hash__) - order=True: generates __lt__, __le__, __gt__, __ge__ - slots=True (3.10+): generates __slots__ for memory efficiency - eq=False: skip __eq__ generation

field() for defaults: use field(default_factory=list) for mutable defaults.

Code examples

Basic Dataclass

from dataclasses import dataclass, field
from typing import List

@dataclass
class Point:
    x: float
    y: float
    label: str = 'origin'

p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1)            # Point(x=1.0, y=2.0, label='origin')
print(p1 == p2)      # True (auto __eq__)

# Mutable defaults must use field()
@dataclass
class Student:
    name: str
    grades: List[float] = field(default_factory=list)
    _id: int = field(default=0, repr=False, compare=False)

    def average(self):
        return sum(self.grades) / len(self.grades) if self.grades else 0

s = Student('Alice')
s.grades.extend([85, 90, 95])
print(s)              # Student(name='Alice', grades=[85, 90, 95])
print(s.average())    # 90.0

field(default_factory=list) is required for mutable defaults. repr=False and compare=False exclude _id from __repr__ and __eq__.

Frozen, Order, and __post_init__

from dataclasses import dataclass
from math import sqrt

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

    def __str__(self):
        return f'{self.major}.{self.minor}.{self.patch}'

v1 = Version(1, 2, 3)
v2 = Version(2, 0, 0)

try:
    v1.major = 99  # Raises FrozenInstanceError
except Exception as e:
    print(e)

print(v1 < v2)   # True (order=True)
print(sorted([v2, v1]))  # [Version(1,2,3), Version(2,0,0)]

# __post_init__ for derived fields
@dataclass
class Circle:
    radius: float
    area: float = field(init=False)

    def __post_init__(self):
        self.area = 3.14159 * self.radius  2

c = Circle(5)
print(c.area)  # 78.53975

frozen=True makes instances hashable and usable as dict keys or in sets. __post_init__ computes derived fields after __init__ runs.

Key points

Concepts covered

@dataclass, field(), frozen, post_init, slots