Blog

dataclass vs class vs NamedTuple vs dict

Python gives you four reasonable ways to hold a few related values. Choosing badly is rarely fatal and always mildly annoying, so it is worth ten minutes once.

The four

d = {'x': 3, 'y': 4}
print(d, d['x'])
print('typo is silent:', d.get('z'))
try:
    d['z']
except KeyError as err:
    print('KeyError:', err)
{'x': 3, 'y': 4} 3
typo is silent: None
KeyError: 'z'
class P1:
    def __init__(self, x, y): self.x, self.y = x, y
print(P1(3,4).x)

@dataclass
class P2:
    x: int
    y: int
print(P2(3,4), P2(3,4) == P2(3,4))

class P3(NamedTuple):
    x: int
    y: int
p3 = P3(3,4)
print(p3, p3.x, tuple(p3), p3 == (3,4))
3
P2(x=3, y=4) True
P3(x=3, y=4) 3 (3, 4) True

The dataclass gives you __init__, __repr__ and __eq__ from the annotations. The NamedTuple gives you all that plus immutability, tuple unpacking, and equality with plain tuples.

Mutability

p2 = P2(3,4); p2.x = 99
print('dataclass mutable:', p2)
try:
    p3.x = 99
except AttributeError as err:
    print('NamedTuple immutable:', type(err).__name__ + ':', err)

@dataclass(frozen=True)
class P4:
    x: int
    y: int
print('frozen dataclass hashable:', hash(P4(3,4)) == hash(P4(3,4)))
dataclass mutable: P2(x=99, y=4)
NamedTuple immutable: AttributeError: can't set attribute
frozen dataclass hashable: True

frozen=True gets you a dataclass with a NamedTuple’s guarantees, and hashability with it.

The deciding question: what happens to a typo

This is the one that actually matters day to day.

p = P2(3,4)
p.z = 5                      # a plain/dataclass object accepts anything
print('dataclass accepts a typo:', p.z)
try:
    P3(3,4).z
except AttributeError as err:
    print('NamedTuple rejects it:', type(err).__name__ + ':', err)
dataclass accepts a typo: 5
NamedTuple rejects it: 'P3' object has no attribute 'z'

A dict silently returns None from .get(). A plain class or dataclass silently accepts a new attribute you never declaredp.z = 5 is not an error, so a misspelled assignment creates a field nobody reads. A NamedTuple refuses both.

That is the strongest argument for NamedTuple or a frozen dataclass on data you pass around: the typo fails where you made it, not three functions later. Part 10 shows how __slots__ gives a mutable class the same protection.

Memory

print('dict       ', sys.getsizeof({'x':3,'y':4}))
print('NamedTuple ', sys.getsizeof(P3(3,4)))
print('class      ', sys.getsizeof(P1(3,4)) + sys.getsizeof(P1(3,4).__dict__))
print('dataclass  ', sys.getsizeof(P2(3,4)) + sys.getsizeof(P2(3,4).__dict__))
dict        184
NamedTuple  56
class       328
dataclass   304

A NamedTuple is a tuple with names, so it carries no per-instance __dict__. Classes and dataclasses do, and it dominates their size.

Treat these numbers as a shape, not a benchmark — they vary by version and by what is stored. The shape is stable: NamedTuple is much smaller, dict is in between, objects with a __dict__ are largest. It only matters at hundreds of thousands of instances.

How to choose

dict — the shape is genuinely dynamic, comes from JSON, or you do not know the keys ahead of time. Stop converting external JSON into objects out of habit.

NamedTuple — a small immutable value you pass around and compare. Cheapest, safest against typos, works with tuple unpacking.

dataclass — the default for anything you wrote yourself with more than about three fields, or that needs to change after construction. Add frozen=True unless you have a reason not to.

plain class — when behaviour matters more than the data: methods, validation in __init__, computed properties. That is Part 1’s bank account, and it is a different job.

What to remember

  • A dict typo is silent; a class or dataclass typo silently creates a field.

  • NamedTuple and frozen=True dataclasses reject both.

  • Reach for a dataclass by default, frozen=True unless something needs to mutate.

  • Keep external JSON as a dict until you have a reason to give it a type.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.