ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
Topics
sre111localhost lab102performance70python58javascript47bundle size36benchmarking35node.js35web development21typescript19benchmark16content creation15All topics
About
Loading article…

ShopperCove

Hands-on notes on developer tools, written after the tool has actually been run.

All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove

On this page

  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table (p50 ops/s)
  5. Semantics (why the gap exists)
  6. Reading it for SRE work
  7. Nested cost
  8. Manual vs asdict
  9. Pitfalls
  10. Reproduce
  11. Limits
  12. Takeaway
  1. Blog

dataclasses.asdict vs vars: Localhost Lab

1 October 2026·4 min read

Summary
On this page
  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table (p50 ops/s)
  5. Semantics (why the gap exists)
  6. Reading it for SRE work
  7. Nested cost
  8. Manual vs asdict
  9. Pitfalls
  10. Reproduce
  11. Limits
  12. Takeaway

Intro — what this post promises

Turn dataclasses into dicts with dataclasses.asdict vs vars() / dict(__dict__) / manual fields. This lab reports ops/s on Linux localhost, plus deep-vs-shallow behavior checks.

Related links:

  • 01shoppercove.comxml etree vs json localhost lab
  • 02shoppercove.comlogging formatter vs fstring localhost lab
  • 03shoppercove.comfractions vs float localhost lab
  • 04shoppercove.commath fsum vs sum localhost lab
  • 05shoppercove.comsqlite3 vs shelve localhost lab
  • 06shoppercove.comgc collect cost localhost lab
  • 07shoppercove.comcopy copy vs dict copy localhost lab
  • 08shoppercove.comhtml parser vs regex localhost lab

Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. Differentiates from dataclass-replace (lab 110) and slots (lab 43) — focus is asdict deepcopy semantics vs cheap vars.

Verdict up front (n=50000): vars flat ~26244997 ops/s; asdict flat ~712136; manual ~12349533; nested asdict ~502824 vs vars ~26083074.


Arms

ArmPattern
asdict(flat)recursive deepcopy-ish
vars(flat)shallow attribute dict
dict(o.__dict__)shallow copy of mapping
manual field dictexplicit keys
asdict(nested)nested Event → dict
vars(nested)Event object still inside

Seven rounds, p50.


Lab topology

n=50000 · flat Event + Nested(Event) · 7 rounds · p50
metric: ops/s = n / p50_s

Script: lab-evidence/130-dataclass-asdict-vs-vars/results/run_lab.py.


Lead table (p50 ops/s)

Armops/s
vars flat26244997
dict(dict) flat9262266
manual flat12349533
asdict flat712136
vars nested26083074
asdict nested502824

vars led by roughly 36.9× on the flat arm — because it does not deep-copy.


Semantics (why the gap exists)

  • asdict(nested)["event"] is a dict; vars(nested)["event"] is still an Event.
  • asdict tags list is a new list (asdict_new_list=True); vars shares the same list (vars_same_list=True).

If you JSON-dump for a wire format, you usually want asdict (or a dedicated serializer). If you only need a shallow attribute bag for logging filters, vars is enough — and far cheaper.


Reading it for SRE work

  • Export/metrics payload → asdict (or manual + json.dumps) so nested dataclasses become JSON-safe.
  • Hot path “mirror attributes” → vars / __dict__ copy; never mutate shared lists casually.
  • Lab 110 covers replace; this post is dict projection cost.
  • Slots instances without __dict__ break vars — know your type.

Document which projection your exporters use so on-call does not “optimize” asdict into vars and ship non-JSON Event objects.


Nested cost

Nested asdict fell to ~502824 ops/s while nested vars stayed near ~26083074. Depth multiplies asdict work; vars stays O(fields on the outer object).



Manual vs asdict

A hand-built field dict hit ~12349533 ops/s — much closer to vars than to asdict, because it still shares the tags list reference unless you copy it yourself. Use manual dicts when you want a stable, shallow export schema without recursive deepcopy. Reach for asdict when nested dataclasses must become plain dict/list trees for JSON or msgpack.

If exporters disagree (one uses vars, one uses asdict), you will debug “sometimes tags mutate upstream” pages — lock one helper in the shared library.


Pitfalls

  • Treating vars output as a deep snapshot.
  • Mutating vars(obj)["tags"] and surprising the dataclass.
  • Using asdict in a per-request hot path without measuring.
  • Forgetting slots / __dict__-less objects.

Reproduce

python3 lab-evidence/130-dataclass-asdict-vs-vars/results/run_lab.py

Evidence: summary.json, summary.txt.


Limits

One Linux box. Stdlib dataclasses only. Not pydantic/msgspec.


Takeaway

vars ~26244997 ops/s vs asdict ~712136 on flat events — pick asdict when you need nested dicts and copied containers; pick vars when a shallow view is enough.

dataclasses.asdictvarsdataclasspythonlocalhost labsreops/s

Lab evidence

What I found running this

Lab 1 Oct 2026 IST. Python 3.13.5. n=50000: vars 26244997 ops/s; asdict 712136; nested asdict 502824. asdict deep-copies; vars shallow. Affiliates: 0. Evidence: lab-evidence/130-dataclass-asdict-vs-vars/.

Notes when a lab post goes up

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

Keep reading

  • platform vs os.uname Inventory: Localhost Lab

    Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.

    Summary1 Oct 20264 min read

  • cmath vs math.hypot Magnitudes: Localhost Lab

    Hands-on cmath vs math.hypot magnitude ops lab: real ops/s for abs, polar, and phase, measured on Linux localhost today in this hands-on lab for SREs.

    Summary1 Oct 20263 min read

  • uuid.uuid4 vs uuid.uuid1: Localhost Lab

    Hands-on uuid.uuid4 vs uuid.uuid1 ID generation lab: real ops/s plus version/node checks, measured on Linux localhost today in this hands-on lab for SREs.

    Summary1 Oct 20263 min read