ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 80

  1. Blog
  2. /Observability & SRE

dataclass vs slots vs dict: Python Object Lab

Hands-on Python dataclass vs slots vs dict lab: instantiation and attribute ops/s plus RSS for classic, slots, dataclass, namedtuple, dict on localhost.

Aditya Challa·30 September 2026·6 min read

Lab
On this page
  1. Intro — what this post promises
  2. Types under test
  3. Lab topology
  4. Lead table — instantiation (p50 ops/s)
  5. Lead table — attribute access (p50 ops/s)
  6. Memory — 200k objects
  7. How to read these numbers
  8. Pitfalls we hit (or avoided)
  9. Practical checklist
  10. Methodology footnote
  11. Versions pinned
  12. When dict still wins
  13. slots construct tax
  14. Ratio cheatsheet (vs classic)
  15. Bottom line for API design
  16. Verdict

Intro — what this post promises

Should that record be a dict, a dataclass, a slotted class, or a namedtuple? Folklore says slots save RAM and dicts are slow. This lab times instantiation and attribute access, then estimates bytes per object.

This is a hands-on lab with measured numbers:

  1. Construct ops/s for classic class, __slots__, @dataclass, @dataclass(slots=True), namedtuple, dict, SimpleNamespace.
  2. Attribute / key read ops/s (a+b).
  3. Allocate 200k objects — tracemalloc peak + VmRSS delta.

Related links:

  • json vs orjson vs msgpack localhost lab
  • fork COW RSS vs spawn localhost lab
  • python re vs str methods localhost lab
  • process vs thread pool GIL localhost lab
  • Why your average latency graph is lying (p50 / p95 / p99)

Lab honesty (1 Oct 2026 IST): Python 3.13.5. Four fields (int, int, str, str). Affiliates: 0. Microbench — not a pydantic/ORM study.

Verdict up front: attr access is nearly tied (~20.4–20.9M/s for class/dataclass/slots). Memory is where slots win (~106–212 B/obj vs dict ~295, classic ~425). Instantiation: dict/dataclass slightly beat classic; namedtuple trailed.


Types under test

KindNotes
classicnormal class + __init__
slotted__slots__ = (a,b,c,d)
dataclass@dataclass
dataclass_slots@dataclass(slots=True)
namedtuplecollections.namedtuple
dictplain {"a":...}
SimpleNamespaceconvenience object

Related links:

  • json vs orjson vs msgpack localhost lab

Lab topology

Instantiation: 500k constructs / timed batch (7 repeats)
Attribute: 2M reads of a+b on one prebuilt object
RSS: list of 200k objects; tracemalloc + /proc VmRSS delta

Script: lab-evidence/44-dataclass-vs-slots/results/run_lab.py.


Lead table — instantiation (p50 ops/s)

Kindops/sns/opvs classic
dict10 297 168971.07×
dataclass10 163 726981.05×
classic9 655 3481041.00×
dataclass(slots)8 310 5371200.86×
slotted7 607 1391320.79×
SimpleNamespace7 309 8481370.76×
namedtuple6 307 2491590.65×

Lead table — attribute access (p50 ops/s)

Kindops/svs classic
slotted20 909 6811.01×
classic20 615 5471.00×
dataclass(slots)20 601 9691.00×
dataclass20 410 5820.99×
dict17 971 5170.87×
namedtuple16 159 9960.78×
SimpleNamespace14 588 5610.71×

Access speed is not why you pick slots on 3.13 for this shape — memory is.

Related links:

  • Why your average latency graph is lying (p50 / p95 / p99)

Memory — 200k objects

Kindtracemalloc peakRSS Δ~B/obj
dataclass(slots)25.9 MB20 744 KB~106
slotted25.9 MB41 368 KB~212
namedtuple29.0 MB23 312 KB~119
dataclass33.6 MB31 680 KB~162
classic33.6 MB83 004 KB~425
dict48.8 MB57 672 KB~295
SimpleNamespace56.5 MB68 372 KB~350

VmRSS deltas are noisy (allocator reuse); tracemalloc peaks and slotted vs dict contrast are the durable lesson.

Related links:

  • fork COW RSS vs spawn localhost lab
  • process vs thread pool GIL localhost lab

How to read these numbers

  • Hot attribute loops: class / dataclass / slots are peers.
  • Millions of small records: prefer dataclass(slots=True) or slots for RAM.
  • Flexible JSON-shaped data: dict still fine (fastest inst here).
  • namedtuple: immutable + lighter than classic, slower attr than slots.

Pitfalls we hit (or avoided)

  1. Claiming slots always instantiate faster — they were slower to construct here.
  2. Trusting only VmRSS — report tracemalloc too.
  3. Extrapolating to huge objects — four small fields only.
  4. Ignoring typed dataclass codegen — 3.13 is fast without slots for CPU.
  5. SimpleNamespace as “free object” — heavier and slower attr here.

Practical checklist

  • Default new structured records: @dataclass (add slots=True if counts are huge).
  • Keep dicts at JSON boundaries; convert once if needed.
  • Don’t rewrite for attr µs — rewrite for RAM or API clarity.
  • Re-bench on your Python minor version.
  • Pair with orjson lab if dict↔JSON dominates.

Related links:

  • json vs orjson vs msgpack localhost lab
  • sha256 vs blake2 xxhash localhost lab
  • atomic rename vs overwrite localhost lab
  • asyncio vs threads IO concurrency lab

Methodology footnote

Instantiation builds throwaway objects in a tight loop. Attribute tests reuse one instance. RSS experiment holds a list of 200k instances, checksums .a / ["a"], then drops the list.


Versions pinned

  • Python 3.13.5
  • Fields: two ints + two short strings

If your heap is full of tiny records, slots are a memory feature with a small construct tax — not an attribute turbo button on this version.


When dict still wins

At JSON/API edges, dict is the native shape. This lab’s construct win for dict (~10.3M/s) means “don’t fear dict for short-lived request objects.” Convert to slotted dataclasses when you retain millions in memory (caches, graphs, feature rows). The orjson lab pairs naturally: decode to dict fast, then materialize structured objects only if lifetime justifies it.

slots construct tax

Both manual __slots__ and dataclass(slots=True) instantiated slower than classic/dataclass here (0.79–0.86×). You buy a smaller __dict__-less layout, not a faster __init__. If startup allocates a huge graph once, pay the tax once; if you churn objects every request, measure — plain dataclass may be the happier default on 3.13.

Ratio cheatsheet (vs classic)

Instantiate: dict 1.07× · dataclass 1.05× · slots-dc 0.86× · slotted 0.79× · namedtuple 0.65×

Attribute: slotted 1.01× · slots-dc 1.00× · dataclass 0.99× · dict 0.87× · namedtuple 0.78×

Use this when someone claims “slots are always faster” — say “faster for RAM; attribute peer; construct slightly slower here.”

Re-run after upgrading Python — dataclass codegen and specialty dictionaries change across minors.

Bottom line for API design

Expose dicts at boundaries; store slotted dataclasses inside long-lived services. That hybrid matches both the construct and RSS tables without pretending attribute access alone will save the quarterly cloud bill.

Verdict

On 3.13.5, attribute access for classic / dataclass / slots sat near ~20.5–20.9M/s while dict lagged ~13%. dataclass(slots) used ~106 B/obj versus dict ~295. Instantiate with dict/dataclass for speed; slot when RSS matters.

Evidence: lab-evidence/44-dataclass-vs-slots/results/. Affiliates: 0.

dataclass slotspython __slots__namedtuple vs dictattribute accessobject allocationlocalhost labsrecpython

Lab evidence

What I found running this

Lab 1 Oct 2026 IST. Python 3.13.5. Inst ops/s: dict 10.3M; dataclass 10.2M; classic 9.7M; dataclass(slots) 8.3M; slotted 7.6M; namedtuple 6.3M. Attr: slotted 20.9M; classic/dataclass/slots ~20.4-20.6M; dict 18.0M; namedtuple 16.2M. RSS 200k objs: dataclass_slots ~106 B/obj; slotted ~212; namedtuple ~119; dataclass ~162; dict ~295; classic ~425 (tracemalloc peaks 25.9 vs 33.6 vs 48.8 MB). Affiliates: 0. Evidence: lab-evidence/44-dataclass-vs-slots/.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 15

    attrgetter vs getattr: Hot Loop Lab

    Hands-on operator.attrgetter vs builtin getattr lab: real ops/s for extract and sort by attribute versus direct access, measured on Linux localhost only.

    Observability & SRE · 30 Sept 2026

  • Plate 12

    islice vs list Slice Windows: Localhost Lab

    Hands-on itertools.islice vs list slice window lab: real ops/s taking ranges from sequences, measured on Linux localhost in this hands-on lab for SREs.

    Observability & SRE · 1 Oct 2026

  • Plate 07

    heapq.merge vs sorted(chain): Localhost Lab

    Hands-on heapq.merge vs sorted(chain) multi-way merge: real records/s on pre-sorted lists, measured on Linux localhost today in this hands-on lab for SREs.

    Observability & SRE · 1 Oct 2026

On this page

  1. Intro — what this post promises
  2. Types under test
  3. Lab topology
  4. Lead table — instantiation (p50 ops/s)
  5. Lead table — attribute access (p50 ops/s)
  6. Memory — 200k objects
  7. How to read these numbers
  8. Pitfalls we hit (or avoided)
  9. Practical checklist
  10. Methodology footnote
  11. Versions pinned
  12. When dict still wins
  13. slots construct tax
  14. Ratio cheatsheet (vs classic)
  15. Bottom line for API design
  16. Verdict
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove