Plate 08
functools.partial vs lambda: Call Lab
Aditya Challa4 min read
Intro — what this post promises
Binding a fixed argument: is functools.partial cheaper to call than a lambda or a nested def factory? This lab times bind-once hot calls, callable creation, and map on Linux localhost.
Related links:
- attrgetter vs getattr localhost lab
- itemgetter vs lambda sort localhost lab
- statistics vs manual mean localhost lab
- bisect vs linear lookup localhost lab
- itertools chain vs flatten localhost lab
- Counter vs dict tally localhost lab
- lru_cache hit vs miss localhost lab
- perf_counter vs time localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. N=500,000 calls/arm after binding once (except recreate arm). Affiliates: 0. Direct add(fixed, x) remains the ceiling.
Verdict up front: partial ~15.6M/s vs lambda ~13.8M (~1.13×); direct ~18.8M (~1.20× partial). Recreating a lambda every call drops to ~8.4M (partial ~1.9× faster). Prefer partial for reusable callables; never rebuild wrappers in the hot loop.
Arms
| Arm | Pattern |
|---|---|
| direct | add(FIXED, i) in loop |
| partial | f = partial(add, FIXED); f(i) |
| lambda | f = lambda x, f=FIXED: add(f, x) |
| nested def | factory returns inner |
| recreate lambda | new lambda every iteration |
| map / genexp | higher-order consume |
Lab topology
Script: lab-evidence/70-functools-partial-vs-lambda/results/run_lab.py.
Lead table — hot call (p50)
| Arm | ops/s | ns/op |
|---|---|---|
| direct | 18,814,016 | 53.2 |
| partial | 15,628,038 | 64.0 |
| nested def | 14,534,384 | 68.8 |
| lambda | 13,811,109 | 72.4 |
| lambda recreate each | 8,375,880 | 119.4 |
Bind / create (p50)
| Arm | creates/s | ns/create |
|---|---|---|
| lambda expr | 16,983,125 | 58.9 |
| partial | 11,372,114 | 87.9 |
| nested factory | 10,222,426 | 97.8 |
Lambda is cheaper to create (~1.49× partial) but partial wins on repeated calls.
map / genexp
| Arm | ops/s |
|---|---|
| genexp direct | 21,051,176 |
| map(partial) | 20,294,146 |
| map(nested) | 18,270,198 |
| map(lambda) | 17,592,197 |
map(partial) ~1.15× map(lambda). Two-arg bind: partial still ~1.07× lambda.
Reading it
partialis a thin C wrapper — slightly faster calls than Python lambda/nested here.- Bind once — recreate-each is the real foot-gun (~1.9× tax vs partial).
- Direct still wins if you do not need a callable object.
- Nested def ≈ lambda on call; use it when you want a name / docstring / multi-statement body.
Partial vs default-arg lambda
lambda x, f=FIXED: add(f, x) binds FIXED at definition time — same idea as partial’s frozen args. The gap in this lab is mostly call convention (C partial object vs Python function), not binding semantics. If a teammate reads partial(add, 7) more clearly than a default-arg lambda, ship partial; if the expression is a one-liner in a local scope, lambda is fine. The non-negotiable rule is create the wrapper once.
Pitfalls
lambdainside a loop that captures a changing loop variable — bind with default (x=i) or use partial carefully.- Micro-optimizing partial vs lambda — tens of ns; readability first.
- Ignoring creation cost when you build millions of short-lived wrappers — lambda create was faster here.
- Assuming partial is always free — still a call vs inlining direct args.
When to pick what
| Need | Prefer |
|---|---|
Reusable callable for map/key= | functools.partial |
| Tiny one-off expression | lambda |
| Multi-line / named closure | nested def |
| Hottest path, no callable needed | direct call |
Reproduce
Evidence: /workspace/lab-evidence/70-functools-partial-vs-lambda/results/.
Closing
Partial edges lambda on hot calls; bind once. On this box partial hit ~15.6M/s vs lambda ~13.8M (~1.13×) and crushed per-iteration lambda rebuild by ~1.9×. Use partial for stable wrappers; keep direct calls when a function object is optional.
Lab evidence
What I found running this
Lab 1 Oct 2026 IST. Python 3.13.5; N=500000. call partial 15.6M vs lambda 13.8M (~1.13x); direct 18.8M (~1.20x partial); recreate-lambda each call ~1.9x slower than partial. Affiliates: 0. Evidence: lab-evidence/70-functools-partial-vs-lambda/.
Related links
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
Plate 50
signal vs threading.Event Wakeup: Localhost Lab
Hands-on signal SIGUSR1 vs threading.Event wakeup lab: real p50 latency in microseconds, measured on Linux localhost today in this hands-on lab for SREs.
1 Oct 2026
Plate 76
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.
1 Oct 2026