Plate 10
Enum vs Constants: Access Cost Lab
Aditya Challa4 min read
Intro — what this post promises
How expensive is enum.Enum compared with module-level ints/strings? This lab measures attribute / value reads, equality, membership, Enum(value) construction, IntFlag bitwise OR, and a rare class-creation path on Linux localhost.
Related links:
- itemgetter vs lambda sort localhost lab
- ThreadPoolExecutor vs sequential localhost lab
- set vs list membership localhost lab
- dataclass vs slots vs dict localhost lab
- lru_cache hit vs miss localhost lab
- perf_counter vs time localhost lab
- array vs list ints localhost lab
- itertools vs python loops localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. N=200,000 loop iters for hot arms. Read arms count one “bundle” of three status reads per op. Affiliates: 0. Enum still wins on typed APIs and exhaustiveness — this is cost, not a ban.
Verdict up front: module int consts ~30M/s bundles vs Enum.value ~4.0M (~7.4×); IntEnum ~16M (~1.82×). IntFlag OR ~32× behind bare ints. Prefer Enum for API clarity; use IntEnum when you need int-like speed + typing.
Arms
| Arm | What | ||
|---|---|---|---|
| const int / str | module-level STATUS_OK = 1 / "ok" | ||
Enum | Status.OK.value / is / Status(v) / Status["OK"] | ||
IntEnum | int-compatible members | ||
IntFlag | `READ | WRITE | EXEC` |
| membership | v in {1,2,3} vs values-set from Enum | ||
| create ×500 | define Enum class vs three assignments |
Lab topology
Script: lab-evidence/61-enum-vs-constants/results/run_lab.py.
Lead table — read / compare (p50)
| Arm | ops/s | ns/op |
|---|---|---|
| const int read (×3 bundle) | 29,537,830 | 33.9 |
| IntEnum attr read | 16,273,592 | 61.4 |
Enum .value read | 4,002,826 | 249.8 |
const int == | 24,457,382 | 40.9 |
Enum member is | 17,528,095 | 57.1 |
IntEnum == int | 16,485,516 | 60.7 |
str const == | 29,262,997 | 34.2 |
Membership, construct, flags
| Arm | ops/s | ns/op |
|---|---|---|
v in const set | 32,045,450 | 31.2 |
v in Enum values set | 31,778,757 | 31.5 |
Status(v) construct | 4,777,720 | 209.3 |
StatusInt(v) | 4,741,658 | 210.9 |
Status["OK"] name lookup | 18,847,036 | 53.1 |
| const bit OR | 30,536,230 | 32.7 |
| IntFlag OR | 941,054 | 1062.6 |
Membership via a precomputed values set matched const sets (~1.01×). Calling Status(v) every time is the expensive validation path (~209 ns).
Creation (rare path)
| Arm | ops/s | ns/op |
|---|---|---|
| module consts ×500 | 66,050,190 | 15.1 |
| Enum class ×500 | 30,799 | 32468.7 |
Metaclass work: consts ~2145× “faster” — irrelevant at import time once; relevant if you generate enums in a hot loop (don’t).
Reading it
IntEnumcloses most of the gap vs plain ints on read (~1.82× vs Enum.value’s ~7.4×).- Compare members with
is/ identity when you already hold Enum objects — cheap; don’t call.valueunless you need the int. - IntFlag is convenience, not a bit-twiddling speedup — ~32× behind bare int OR here.
- API boundary: accept Enum in public functions; keep hot inner loops on ints if a profiler complains.
Pitfalls
- Banishing Enum after a microbench — clarity and invalid-value rejection matter more than 200 ns.
EnumvsIntEnummixups — plain Enum is not an int;Status.OK == 1is False.- Rebuilding membership sets every call — cache
{m.value for m in Status}. - Creating Enum classes dynamically in a loop — metaclass tax is huge.
When to pick what
| Need | Prefer |
|---|---|
| Public status / API | Enum |
| Wire ints + typed names | IntEnum |
| Permission bitmasks | IntFlag (clarity) or ints (speed) |
| Hottest numeric loop | module ints |
Reproduce
Evidence: /workspace/lab-evidence/61-enum-vs-constants/results/.
Closing
Enums cost a little; IntEnum costs less. On this box const int reads were ~7.4× Enum.value and ~1.82× IntEnum; IntFlag OR trailed bare bits by ~32×. Use Enum at boundaries; profile before ripping types out of a hot path.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5; N=200000. const int read 29.5M vs Enum.value 4.0M (~7.4x); vs IntEnum ~1.82x; IntFlag OR vs int bits ~32x; Enum class create vs module consts ~2145x. Affiliates: 0. Evidence: lab-evidence/61-enum-vs-constants/.
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 80
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.
Observability & SRE · 30 Sept 2026
Plate 17
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.
1 Oct 2026