ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 15

  1. Blog
  2. /Observability & SRE

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.

Aditya Challa·30 September 2026·4 min read

Lab
On this page
  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — extract (p50)
  5. Lead table — sort (p50)
  6. Reading it
  7. Direct vs callable: when the API forces a function
  8. Pitfalls
  9. When to pick what
  10. Reproduce
  11. Closing

Intro — what this post promises

In a hot loop, is operator.attrgetter worth it over getattr, or should you just write .score? This lab times extract-to-list and list.sort(key=...) on Linux localhost for dataclass rows (plain + slots=True).

Related links:

  • itemgetter vs lambda sort localhost lab
  • Counter vs dict tally localhost lab
  • dataclass vs slots vs dict localhost lab
  • frozenset vs set membership localhost lab
  • bytes vs bytearray localhost lab
  • enum vs constants localhost lab
  • itertools vs python loops localhost lab
  • perf_counter vs time localhost lab

Lab honesty (1 Oct 2026 IST): Python 3.13.5. N=50,000. Distinct from the itemgetter lab: focus is getattr builtin vs attrgetter vs direct attribute. Affiliates: 0.

Verdict up front: direct .score ~83M/s extract; attrgetter ~47M; getattr ~41M (direct ~2.0× getattr; attrgetter ~1.14× getattr). Sort gaps shrink: attrgetter vs getattr-lambda ~1.12×.


Arms

ArmPattern
direct[r.score for r in rows] / key=lambda r: r.score
attrgettermap(attrgetter("score"), rows) / key=ag
getattr[getattr(r, "score") for r in rows]
getattr defaultgetattr(r, "score", None)
lambda mapmap(lambda r: r.score, rows)
dynamic namename in a string; attrgetter(name) vs getattr

Lab topology

N = 50000 Row / RowSlots dataclasses
extract + sort arms; p50 ops/s (elements)

Script: lab-evidence/65-attrgetter-vs-getattr/results/run_lab.py.


Lead table — extract (p50)

Armops/sns/op
direct .score83,285,44512.0
slots direct89,706,53411.1
attrgetter map46,744,17521.4
slots attrgetter52,466,33819.1
getattr40,941,12224.4
getattr default39,318,99525.4
lambda map34,452,65429.0
attrgetter dynamic32,407,38930.9
getattr dynamic24,770,62440.4

Lead table — sort (p50)

Armelems/sns/elem
key=attrgetter5,029,556198.8
key=lambda r: r.score4,781,393209.1
key=lambda getattr4,483,767223.0
attrgetter multi2,092,820477.8
lambda tuple1,912,574522.9

Reading it

  • Direct attribute wins extract — LOAD_ATTR beats a function call (~1.78× attrgetter, ~2.0× getattr).
  • attrgetter beats getattr when you need a callable (~1.14× extract; ~1.31× with a dynamic name built once).
  • Sort compresses gaps — Timsort dominates; attrgetter still edges getattr-lambda (~1.12×).
  • getattr is for dynamic / optional names — not a faster .attr.

Direct vs callable: when the API forces a function

List comprehensions and straight attribute loads do not need a callable. map, sorted/list.sort key=, and similar APIs do. That is attrgetter’s niche: a tiny C callable bound to one or more names, reusable without a Python lambda wrapper. getattr remains the right tool when the name is data (config key, optional field) — just do not pay for it on a static .score path. Slots rows in this lab were a bit faster on direct access (~90M/s) but the ranking vs getattr stayed the same.


Pitfalls

  1. Using getattr for a fixed name in a hot loop — write .score or attrgetter.
  2. Rebuilding attrgetter(name) every call — bind once (dynamic arm builds once per timed run).
  3. Optimizing sort keys before N hurts — tens of ns inside a ms-scale sort.
  4. Confusing with itemgetter — tuples/dicts vs objects (see related itemgetter lab).

When to pick what

NeedPrefer
Fixed attribute in comprehensiondirect .attr
key= / map callableoperator.attrgetter
Name from config / optional fieldgetattr
Multi-field keyattrgetter("a", "b")

Reproduce

python3 lab-evidence/65-attrgetter-vs-getattr/results/run_lab.py

Evidence: /workspace/lab-evidence/65-attrgetter-vs-getattr/results/.


Closing

Dots first; attrgetter for callables; getattr for dynamism. On this box direct extract ~2.0× getattr and ~1.78× attrgetter; sort attrgetter still ~1.12× a getattr lambda. Reach for getattr only when the attribute name is not known statically.

attrgettergetattroperatorattribute accesssort keypythonlocalhost labsre

Lab evidence

What I found running this

Lab 1 Oct 2026 IST. Python 3.13.5; N=50000. extract direct 83M vs attrgetter 47M vs getattr 41M (direct2.0x getattr; ag1.14x getattr). sort attrgetter vs getattr ~1.12x. Affiliates: 0. Evidence: lab-evidence/65-attrgetter-vs-getattr/.

Notes when a lab post goes up

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

Related links

  • Plate 82

    itemgetter vs lambda: Sort Key Lab

    Hands-on operator.itemgetter vs lambda sort-key lab: real ops/s for extract and list.sort on tuples, dicts, and objects, measured on Linux localhost (lab).

    Observability & SRE · 30 Sept 2026

  • Plate 18

    fnmatch vs re Name Filter: Localhost Lab

    Hands-on fnmatch.filter vs re.compile name-list filtering measured on Linux localhost.

    Observability & SRE · 30 Sept 2026

  • Plate 84

    tarfile vs zipfile Create+Extract: Localhost Lab

    Hands-on tarfile vs zipfile create+extract lab: real MB/s on a mixed small-file fixture (uncompressed tar vs zip), measured on Linux localhost for SREs.

    Observability & SRE · 30 Sept 2026

On this page

  1. Intro — what this post promises
  2. Arms
  3. Lab topology
  4. Lead table — extract (p50)
  5. Lead table — sort (p50)
  6. Reading it
  7. Direct vs callable: when the API forces a function
  8. Pitfalls
  9. When to pick what
  10. Reproduce
  11. Closing
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove