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.NamedTuplewith class syntax: the same runtime type, plus annotations, defaults and methods:
class P(NamedTuple):
x: int
y: int = 0@dataclassclasses: regular classes with generated__init__,__repr__and__eq__; mutable unlessfrozen=True; optionalslots=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=Truecome 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 FalseRemember
- 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.