namedtuple vs NamedTuple vs dataclass

Difficulty: Intermediate

Question

What are the differences between namedtuple, typing.NamedTuple, and dataclass?

Answer

All three create structured record types, but differ in features:

namedtuple (collections): immutable tuple subclass, memory-efficient, fast, no type hints typing.NamedTuple: like namedtuple but with type annotations and method definitions dataclass: mutable by default, supports default values, methods, inheritance, not a tuple

When to use: - namedtuple/NamedTuple: read-only records, unpacking, tuple compatibility needed - dataclass: mutable records with behavior, validation, complex defaults - dataclass(frozen=True): immutable dataclass (not tuple, but hashable)

Code examples

All Three Side by Side

from collections import namedtuple
from typing import NamedTuple
from dataclasses import dataclass

# 1. collections.namedtuple
Point1 = namedtuple('Point1', ['x', 'y'])

# 2. typing.NamedTuple - with types and methods
class Point2(NamedTuple):
    x: float
    y: float
    label: str = 'point'

    def distance_from_origin(self):
        return (self.x2 + self.y2)  0.5

# 3. dataclass
@dataclass
class Point3:
    x: float
    y: float
    label: str = 'point'

    def distance_from_origin(self):
        return (self.x2 + self.y2)  0.5

p1 = Point1(3.0, 4.0)
p2 = Point2(3.0, 4.0)
p3 = Point3(3.0, 4.0)

# Tuple behavior (namedtuple and NamedTuple)
print(isinstance(p1, tuple))  # True
print(isinstance(p2, tuple))  # True
print(isinstance(p3, tuple))  # False

x, y = p2  # Unpacking works
print(x, y)

# Immutability
try:
    p2.x = 99  # AttributeError
except AttributeError:
    print('NamedTuple is immutable')

p3.x = 99  # dataclass is mutable by default
print(p3.x)

namedtuple and NamedTuple are tuples - they support unpacking and are immutable. dataclass is a regular class - mutable and not a tuple.

Memory and Performance

import sys
from collections import namedtuple
from typing import NamedTuple
from dataclasses import dataclass

Point = namedtuple('Point', ['x', 'y', 'z'])

class TypedPoint(NamedTuple):
    x: float; y: float; z: float

@dataclass
class DPoint:
    x: float; y: float; z: float

@dataclass(slots=True)
class SPoint:
    x: float; y: float; z: float

p1 = Point(1, 2, 3)
p2 = TypedPoint(1, 2, 3)
p3 = DPoint(1, 2, 3)
p4 = SPoint(1, 2, 3)

print('namedtuple:', sys.getsizeof(p1))   # 72
print('NamedTuple:', sys.getsizeof(p2))   # 72
print('dataclass:', sys.getsizeof(p3))    # 48 + dict overhead
print('dataclass slots:', sys.getsizeof(p4))  # 56

# Conversion
print(dict(p1._asdict()))  # {'x': 1, 'y': 2, 'z': 3}
print(p1._replace(z=99))   # Point(x=1, y=2, z=99) - new instance

_asdict() converts to OrderedDict. _replace() creates a modified copy (like copy() for frozen dataclasses).

Key points

Concepts covered

namedtuple, NamedTuple, dataclass, tuple, Record Types