Python Part I · Module 11 · Functions
lambda: Small Anonymous Functions
Sorting results by runtime needs a function that pulls the runtime out of one result. It is one expression long, it is used in exactly one place, and giving it a name and three lines of definition is more ceremony than the job deserves. Python has a compact form for writing a function where a function is expected, without naming it. It is worth learning precisely, and it is worth learning its limits just as precisely — because it has essentially one good use in the kind of code this course is about, and reaching for it elsewhere produces code that is shorter and worse.
Intermediate14 min readPythonPart IFundamentalsFunctionslambda
Part I · Module 11 · Chapter 11.14 · lambda: Small Anonymous Functions
1. The Problem It Solves
Chapter 5.5 sorted lists of simple values. Real results are not simple values:
results = [
{"test": "burst", "runtime_s": 450},
{"test": "smoke", "runtime_s": 120},
{"test": "reset", "runtime_s": 90},
]Sorting those by runtime means telling sort what to sort by, and the way you tell it is with a function that, given one result, returns the thing to compare. That function is one expression long and is needed in exactly one place.
2. A lambda Is an Ordinary Function Value
runtime_of = lambda result: result["runtime_s"]
print(type(runtime_of))
print(runtime_of({"test": "smoke", "runtime_s": 120}))<class 'function'>
120<class 'function'> — the same type a def produces. A lambda creates nothing new. It is a second spelling for making a function value, with two restrictions:
- it has no name of its own
- its body is one expression, whose value is returned
The syntax is lambda <parameters>: <expression>. No parentheses around the parameters, no return, no block.
Writing runtime_of = lambda ... as above is legal and is not what lambdas are for — if you are giving it a name, write a def, which also lets you add a docstring. Section 8 returns to this. The assignment above exists only to show the type.
3. The Use That Justifies It
results = [
{"test": "burst", "runtime_s": 450},
{"test": "smoke", "runtime_s": 120},
{"test": "reset", "runtime_s": 90},
]
results.sort(key=lambda r: r["runtime_s"])
for r in results:
print(r["test"], r["runtime_s"])reset 90
smoke 120
burst 450key= takes a function. sort calls it once per item and sorts by whatever comes back — here, the runtime.
The lambda is written exactly where it is used, says what it does in nine characters, and is never referred to again. That is the case where it is the right tool.
Sorting by a different field is a one-word change:
results = [
{"test": "burst", "runtime_s": 450},
{"test": "smoke", "runtime_s": 120},
{"test": "reset", "runtime_s": 90},
]
results.sort(key=lambda r: r["test"])
for r in results:
print(r["test"])burst
reset
smoke4. key= Takes the Function, Not a Call
This is Chapter 11.3 Section 4's rule — a function is a value — arriving where it matters. You hand sort the function itself; sort does the calling:
names = ["burst", "smoke", "cdc"]
names.sort(key=len)
print(names)['cdc', 'burst', 'smoke']key=len with no parentheses. Sorted by length: 3, 5, 5.
Note what happened to the tie. "burst" and "smoke" are both five characters, and they came back in the order they started in. Python's sort is stable: items the key rates equally keep their original relative order. That is a guarantee, not a coincidence, and it is what makes sorting by one field and then another work at all.
Add the parentheses and it breaks, because you called len with no argument before sort ever saw it:
names = ["burst", "smoke", "cdc"]
names.sort(key=len())
print(names)TypeError: len() takes exactly one argument (0 given)The same applies to a lambda: key=lambda r: r["test"] hands over a function, and sort supplies each r.
5. No return, Because the Body Is an Expression
The most common first attempt:
double = lambda n: return n * 2SyntaxError: invalid syntaxreturn is a statement, and a lambda's body must be a single expression — Chapter 3.1's distinction, enforced. The expression's value comes back automatically:
double = lambda n: n * 2
print(double(5))10The same restriction rules out everything else statement-shaped: no if statement, no loop, no assignment, no multiple lines. A conditional expression from Chapter 9.6 is allowed, because that is an expression:
classify = lambda n: "clean" if n == 0 else "failing"
print(classify(0))
print(classify(3))clean
failingThat is already at the edge of readable. Two of them nested would not be.
6. sorted() Takes key= Too
Chapter 5.5 introduced both, and Chapter 11.7 Section 8 explained why there are two. Both accept a key:
results = [
{"test": "burst", "runtime_s": 450},
{"test": "smoke", "runtime_s": 120},
]
by_runtime = sorted(results, key=lambda r: r["runtime_s"])
print(by_runtime[0]["test"])
print(results[0]["test"])smoke
burstsorted() returned a new list and left results in its original order — the non-mutating half of the pair. Use it when the caller's list must not change, which Chapter 11.7 argued should be the default.
Reversing works the same way:
results = [
{"test": "burst", "runtime_s": 450},
{"test": "smoke", "runtime_s": 120},
{"test": "reset", "runtime_s": 90},
]
slowest_first = sorted(results, key=lambda r: r["runtime_s"], reverse=True)
for r in slowest_first:
print(r["test"], r["runtime_s"])burst 450
smoke 120
reset 907. The Length Limit
The canonical guidance for this chapter is blunt and correct: anything longer than a simple key expression deserves a def.
results = [
{"test": "burst", "runtime_s": 450, "status": "FAIL"},
{"test": "smoke", "runtime_s": 120, "status": "PASS"},
{"test": "reset", "runtime_s": 90, "status": "FAIL"},
]
by_status_then_runtime = sorted(
results,
key=lambda r: (r["status"], -r["runtime_s"]),
)
for r in by_status_then_runtime:
print(r["status"], r["test"], r["runtime_s"])FAIL burst 450
FAIL reset 90
PASS smoke 120That one is still readable — a tuple key sorts by the first field, then the second, and the minus reverses the numeric part. Push further and it stops being readable, at which point a named function is better:
def sort_key(result):
"""Order failures before passes, slowest first within each group."""
failed_first = 0 if result["status"] == "FAIL" else 1
return (failed_first, -result["runtime_s"])
results = [
{"test": "burst", "runtime_s": 450, "status": "FAIL"},
{"test": "smoke", "runtime_s": 120, "status": "PASS"},
{"test": "reset", "runtime_s": 90, "status": "FAIL"},
]
for r in sorted(results, key=sort_key):
print(r["status"], r["test"], r["runtime_s"])FAIL burst 450
FAIL reset 90
PASS smoke 120The named version is three lines longer and gained a docstring explaining the ordering — which the lambda had nowhere to put. That is the real cost of a lambda past a certain size: there is no room for the sentence a reader needs.
8. lambda or def
| Question | lambda | def |
|---|---|---|
| Has a name | no | yes |
| Body | one expression | any statements |
| Can carry a docstring | no | yes |
| Can carry type hints | no | yes |
| Reusable elsewhere | only via a name, which defeats it | yes |
| Right when | a short key or callback, used once, inline | everything else |
Never assign a lambda to a name. double = lambda n: n * 2 and def double(n): return n * 2 produce the same thing, and the second can be documented, annotated and found by name in a traceback. If it needs a name, it is a def.
The honest summary: in automation code of the kind this course builds, key= is the use, and it covers nearly all of them.
9. Predict, Run, Observe
# A
f = lambda x: x + 1
print("A:", f(1), type(f))
# B
words = ["ccc", "a", "bb"]
print("B:", sorted(words, key=len))
# C
pairs = [("b", 2), ("a", 3), ("c", 1)]
print("C:", sorted(pairs, key=lambda p: p[1]))
# D
nums = [3, 1, 2]
print("D:", sorted(nums, reverse=True), nums)The verified output:
A: 2 <class 'function'>
B: ['a', 'bb', 'ccc']
C: [('c', 1), ('b', 2), ('a', 3)]
D: [3, 2, 1] [3, 1, 2]A — an ordinary function value, called normally.
B — key=len without parentheses, sorting by length.
C — the lambda takes each tuple and returns its second field, so the sort is by number.
D — sorted left nums untouched, which is the Chapter 11.7 distinction again.
10. Exercises
Exercise 1 — sort by a field. Given a list of dictionaries with test and seed, sort by seed using a lambda, then by test name. Print both orders.
Exercise 2 — the type. Print the type of a lambda and of a def function. Say in one sentence what that tells you about what lambda creates.
Exercise 3 — the parentheses. Write names.sort(key=len()) and record the exact error. Explain in two sentences what sort was handed and what it needed.
Exercise 4 — no return. Write lambda n: return n * 2, record the error, then fix it. Say which Chapter 3.1 distinction the error is enforcing.
Exercise 5 — mutate or not. Sort a list of results both with .sort(key=...) and with sorted(..., key=...), printing the original after each. Say which you would use in a function that was given the list by its caller, and why.
Exercise 6 — two fields. Sort results by status, and within each status by runtime descending, using a tuple key. Then rewrite it as a named function with a docstring and say which you prefer.
Exercise 7 — lambda or def. For each, choose and justify in one sentence: (a) a key extracting one dictionary field (b) a function used in four places (c) a key needing an if and two lookups (d) a key returning the length of a name.
Exercise 8 — remove the name. Find any name = lambda ... and rewrite it as a def with a docstring and type hints. Say what you gained.
11. Summary
lambda <params>: <expression>creates a function value —type()reportsfunction, exactly as adefdoes.- It creates nothing new. It is a second spelling, with two restrictions: no name, and one expression.
- The body is an expression and its value is returned automatically.
returninside one is aSyntaxError. - No statements are allowed — no
ifstatement, no loop, no assignment. A conditional expression is fine, because it is an expression. - The use that justifies it is
key=onsortandsorted. key=takes the function, not a call.key=len, neverkey=len().sorted()returns a new list;.sort()mutates — Chapter 11.7's pair, and both takekey=.- A tuple key sorts by several fields in order, and a minus reverses a numeric one.
- A lambda cannot carry a docstring or type hints, which is what makes it wrong past a short expression.
- Never assign a lambda to a name. If it needs a name, write a
def.
The last chapter of the module puts all of this to work on a real script.
Where this fits
Part of the Python curriculum.
