4 min read
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:
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.
| Arm | Pattern |
|---|---|
asdict(flat) | recursive deepcopy-ish |
vars(flat) | shallow attribute dict |
dict(o.__dict__) | shallow copy of mapping |
| manual field dict | explicit keys |
asdict(nested) | nested Event → dict |
vars(nested) | Event object still inside |
Seven rounds, p50.
Script: lab-evidence/130-dataclass-asdict-vs-vars/results/run_lab.py.
| Arm | ops/s |
|---|---|
| vars flat | 26244997 |
| dict(dict) flat | 9262266 |
| manual flat | 12349533 |
| asdict flat | 712136 |
| vars nested | 26083074 |
| asdict nested | 502824 |
vars led by roughly 36.9× on the flat arm — because it does not deep-copy.
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.
asdict (or manual + json.dumps) so nested dataclasses become JSON-safe.vars / __dict__ copy; never mutate shared lists casually.replace; this post is dict projection cost.__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 asdict fell to ~502824 ops/s while nested vars stayed near ~26083074. Depth multiplies asdict work; vars stays O(fields on the outer object).
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.
vars output as a deep snapshot.vars(obj)["tags"] and surprising the dataclass.asdict in a per-request hot path without measuring.__dict__-less objects.Evidence: summary.json, summary.txt.
One Linux box. Stdlib dataclasses only. Not pydantic/msgspec.
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.
Lab evidence
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/.
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
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
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