Hands-on operator.methodcaller vs getattr vs direct: real calls/s invoking methods on many objects, measured on Linux localhost today in this lab for SREs.
4 min read
Call a method on many objects: operator.methodcaller, getattr(obj, name)(), direct obj.method(), and pre-bound methods. This lab reports calls/s on Linux localhost.
It is not attrgetter (lab 65) or itemgetter (lab 59) — those fetch attributes/items; this invokes methods.
Related links:
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. No Docker. Widget.value() / Widget.add(1).
Verdict up front (10 000 objects): direct value() ~30.38 Mcalls/s; getattr ~17.04; methodcaller ~13.53; prebound calls ~29.68. Use methodcaller for clean map/sorted key hooks; direct calls when the name is fixed in code.
| Arm | Pattern |
|---|---|
| direct | w.value() |
methodcaller("value") | reusable caller |
getattr(w, "value")() | dynamic name |
| bind-each-time | [w.value for w in …] then call |
| prebound | bind once, call many |
add(1) variants | methodcaller with args |
Script: lab-evidence/99-methodcaller-vs-getattr/results/run_lab.py.
| Arm | Mcalls/s |
|---|---|
direct value() | 30.38 |
| prebound then call | 29.68 |
| getattr + call | 17.04 |
methodcaller value | 13.53 |
| bind-each + call | 12.48 |
Direct and prebound sit near the top. methodcaller pays for a portable callable. Binding a fresh method object every outer loop (bound_methods) is the costly anti-pattern.
add(1), n=10 000)| Arm | Mcalls/s |
|---|---|
| direct | 22.44 |
| methodcaller("add", 1) | 16.87 |
| getattr + call | 14.24 |
With a bound argument, methodcaller closes some of the gap vs getattr — still behind a direct call on this box.
| n | direct | methodcaller | getattr | prebound |
|---|---|---|---|---|
| 1 000 | 19.35 | 10.61 | 17.27 | 29.39 |
| 10 000 | 30.38 | 13.53 | 17.04 | 29.68 |
| 50 000 | 29.56 | 19.56 | 15.62 | 28.89 |
Absolute rates move with cache effects; the ordering (direct / prebound ahead of dynamic lookup) is the useful takeaway.
methodcaller shines when an API wants a one-argument callable: sorted(objs, key=methodcaller("score")) or map(methodcaller("strip"), lines). That clarity often matters more than a few Mcalls/s on a microbench. Pair with attrgetter only when you need the attribute itself, not a call.
methodcaller is for higher-order tools (map, sorted(..., key=methodcaller("lower"))) — readable, slight tax.getattr+call suits truly dynamic names; expect overhead vs direct.Evidence: summary.json, summary.txt.
One Linux box. Microbench only — real workloads with I/O will dwarf call overhead. Tiny methods returning ints. Not free-threaded surprises. Not slots vs dict object layouts beyond Widget slots.
At 10 k objects, direct ~30.38 Mcalls/s beat getattr ~17.04 and methodcaller ~13.53. Prefer direct in hot loops; methodcaller when you need a portable callable for APIs that expect a function.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5. n=10k value(): direct 30.38 Mcalls/s; getattr 17.04; methodcaller 13.53; prebound 29.68. Not attrgetter/itemgetter. Affiliates: 0. Evidence: lab-evidence/99-methodcaller-vs-getattr/.
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.
Lab1 Oct 20264 min read
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.
Lab1 Oct 20264 min read
Hands-on mmap MAP_SHARED write+msync vs pwrite region update: real MB/s with durability labels, measured on Linux localhost in this hands-on lab for SREs.
Lab1 Oct 20264 min read