Skip to content
intermediate Phase 2 · Python OOP

Dataclasses & Named Tuples

Simplify class creation with @dataclass decorator and named tuples.

45m
0 problems
Topic Progress 0%

Dataclass Basics

The Problem with Regular Classes

# ❌ Regular class - lots of boilerplate
class PointRegular:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __repr__(self):
        return f'PointRegular(x={self.x}, y={self.y})'
    
    def __eq__(self, other):
        if not isinstance(other, PointRegular):
            return False
        return self.x == other.x and self.y == other.y

# ✅ Dataclass - minimal boilerplate
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

p = Point(1.0, 2.0)
print(p)           # Point(x=1.0, y=2.0)
print(p.x, p.y)   # 1.0 2.0
p2 = Point(1.0, 2.0)
print(p == p2)     # True

What Dataclasses Generate

@dataclass
class Point:
    x: float
    y: float

# Automatically generates:
# def __init__(self, x: float, y: float): ...
# def __repr__(self): ...
# def __eq__(self, other): ...

Field Defaults

from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    age: int
    email: str = ''                    # Default value
    is_active: bool = True             # Default value
    tags: list = field(default_factory=list)  # Mutable default!

user = User('Alice', 30)
print(user)  # User(name='Alice', age=30, email='', is_active=True, tags=[])

# ⚠️ Can't have non-default after default
# @dataclass
# class Bad:
#     a: int       # ❌ SyntaxError
#     b: int = 0   # Non-default follows default

Field Configuration

from dataclasses import dataclass, field

@dataclass
class Config:
    # repr=False - exclude from __repr__
    secret_key: str = field(repr=False)
    
    # compare=False - exclude from __eq__
    created_at: float = field(compare=False)
    
    # default_factory - for mutable defaults
    items: list = field(default_factory=list)
    
    # init=False - not in __init__
    version: int = field(init=False, default=1)

config = Config('abc123', 1.0)
print(config)  # Config(items=[], version=1)
# secret_key not shown (repr=False)

Immutability

@dataclass(frozen=True)
class ImmutablePoint:
    x: float
    y: float

p = ImmutablePoint(1.0, 2.0)
print(p)
# p.x = 3.0  # ❌ FrozenInstanceError

# Can be used as dict key
locations = {p: 'origin'}

Advanced Dataclass Features

Post-Init Processing

from dataclasses import dataclass, field
import math

@dataclass
class Circle:
    radius: float
    
    def __post_init__(self):
        # Run after __init__
        if self.radius < 0:
            raise ValueError('Radius cannot be negative')
    
    @property
    def area(self):
        return math.pi * self.radius ** 2

# circle = Circle(-5)  # ❌ ValueError: Radius cannot be negative
c = Circle(5)
print(f'Area: {c.area:.2f}')  # Area: 78.54

Ordering

@dataclass(order=True)
class Student:
    grade: float
    name: str
    
    def __post_init__(self):
        # Sort by grade first, then name
        self._sort_key = (-self.grade, self.name)

students = [
    Student(3.5, 'Alice'),
    Student(4.0, 'Bob'),
    Student(3.5, 'Charlie'),
    Student(4.0, 'Alice'),
]

# Sorting works automatically
print(sorted(students))
# [Student(grade=4.0, name='Alice'), Student(grade=4.0, name='Bob'), 
#  Student(grade=3.5, name='Alice'), Student(grade=3.5, name='Charlie')]

Inheritance

@dataclass
class Base:
    x: int
    y: int

@dataclass
class Child(Base):
    z: int

c = Child(1, 2, 3)
print(c)  # Child(x=1, y=2, z=3)

Dataclass vs Named Tuple vs Regular Class

from dataclasses import dataclass
from collections import namedtuple

# Named Tuple - immutable, lightweight
PointNT = namedtuple('Point', ['x', 'y'])

# Dataclass - mutable (by default), feature-rich
@dataclass
class PointDC:
    x: float
    y: float

# Regular Class - most flexible
class PointClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

# Use NamedTuple when:
# - You need immutability
# - You want memory efficiency
# - You need tuple behavior

# Use Dataclass when:
# - You want auto-generated __init__, __repr__, __eq__
# - You need mutable state
# - You want field-level configuration

# Use Regular Class when:
# - You need complex logic
# - You need custom methods
# - Dataclass features don't fit

Named Tuples

Basic Named Tuple

from collections import namedtuple

# Define
Point = namedtuple('Point', ['x', 'y'])

# Create
p = Point(1, 2)

# Access
print(p.x, p.y)  # 1 2
print(p[0], p[1])  # 1 2

# Unpack
x, y = p

# Immutable
# p.x = 3  # ❌ AttributeError

Advanced Named Tuples

from collections import namedtuple

# With defaults
Point = namedtuple('Point', ['x', 'y'], defaults=[0, 0])
print(Point())     # Point(x=0, y=0)
print(Point(5))    # Point(x=5, y=0)

# From dict
d = {'x': 1, 'y': 2}
p = Point(**d)

# Convert to dict
print(p._asdict())  # {'x': 1, 'y': 2}

# Create new with changes
p2 = p._replace(x=10)
print(p2)  # Point(x=10, y=2)

# Get field names
print(Point._fields)  # ('x', 'y')

Typed Named Tuples (Python 3.6+)

from typing import NamedTuple

class Point(NamedTuple):
    x: float
    y: float
    label: str = 'origin'  # Default value

p = Point(1.0, 2.0)
print(p)  # Point(x=1.0, y=2.0, label='origin')

# Type hints work
p: Point = Point(1.0, 2.0)

When to Use Each

# ✅ Use NamedTuple for:
# - Lightweight immutable records
# - Returning multiple values
# - Dictionary keys

# ✅ Use dataclass for:
# - Mutable objects
# - Complex initialization
# - When you need methods

# ✅ Use regular class for:
# - Complex behavior
# - Inheritance hierarchies
# - When you need full control