4 min read
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:
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.
| 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 |
Script: lab-evidence/70-functools-partial-vs-lambda/results/run_lab.py.
| 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 |
| 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.
| 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.
partial is a thin C wrapper — slightly faster calls than Python lambda/nested here.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.
lambda inside a loop that captures a changing loop variable — bind with default (x=i) or use partial carefully.| 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 |
Evidence: /workspace/lab-evidence/70-functools-partial-vs-lambda/results/.
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
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/.
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 signal SIGUSR1 vs threading.Event wakeup lab: real p50 latency in microseconds, 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