namedtuple vs NamedTuple vs dataclass

collections · Advanced · 10 min

Picking the right record type affects mutability, memory, typing and API stability.

Three ways to define a record:

  • collections.namedtuple("P", "x y"): dynamic, untyped, a tuple subclass.
  • typing.NamedTuple with class syntax: the same runtime type, plus annotations, defaults and methods:
class P(NamedTuple):
    x: int
    y: int = 0
  • @dataclass classes: regular classes with generated __init__, __repr__ and __eq__; mutable unless frozen=True; optional slots=True, order=True, kw_only=True.

Trade-offs:

  • Tuple behaviour is a feature and a risk. NamedTuples unpack, index and compare equal to plain tuples: P(1, 2) == (1, 2). A dataclass compares only to the same class. Adding a field to a NamedTuple breaks callers that unpack it positionally.
  • Mutability: NamedTuple is always immutable; dataclasses choose.
  • Memory: NamedTuples are as compact as tuples; dataclasses with slots=True come close.
  • Validation and computed fields: dataclasses have __post_init__; NamedTuples can't override __init__, only __new__.

Rule of thumb: use NamedTuple when the record genuinely is a tuple (return values that callers unpack, rows, coordinates), and dataclasses for domain objects that may grow.

Example

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

PointA = namedtuple("PointA", "x y")

class PointB(NamedTuple):
    x: int
    y: int = 0
    def norm2(self):
        return self.x ** 2 + self.y ** 2

@dataclass(frozen=True, slots=True)
class PointC:
    x: int
    y: int = 0

print(PointA(1, 2) == (1, 2), PointB(1, 2) == (1, 2))   # True True
print(PointC(1, 2) == (1, 2))                          # False
print(PointB(3, 4).norm2(), PointB(5))                 # 25 PointB(x=5, y=0)
x, y = PointB(7, 8)                                    # unpacking works
print(PointB.__annotations__)          # {'x': <class 'int'>, 'y': <class 'int'>}
print(isinstance(PointB(1), tuple), isinstance(PointC(1), tuple))  # True False

Remember

  • namedtuple and typing.NamedTuple create tuple subclasses; NamedTuple adds types and methods
  • Tuple equality and unpacking make NamedTuple APIs hard to extend
  • Use dataclasses for domain objects; NamedTuple for genuinely tuple-like data

Watch out for

  • Returning a NamedTuple from a public API and later being unable to add a field without breaking unpacking callers.

Check yourself

  1. Which record type compares equal to a plain tuple with the same values?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/typing.html#typing.NamedTuple

Builds on

Unlocks

Nothing builds on this yet.