PYTHON / FUNCTIONS
Lambda expressions
Write single-expression anonymous functions with lambda, pass them as sort and filter keys, and know when a def is the better choice.
What you will learn
- Build a function object inline with lambda params: expression
- Pass lambdas as key= to sorted, max, and min for derived orderings
- Bind a loop variable with a default parameter to avoid late-binding surprises
- Recognise which bodies are illegal: assignment, return, raise, for, while
Understanding Lambda expressions
A lambda is not a special kind of callable; it is an expression that evaluates to an ordinary function object. `f = lambda n: n * n` and a `def f(n): return n * n` produce objects of the same type, both accept the same call syntax, and both follow the LEGB scope rules you already know. The only real differences are that a lambda's body must be one expression whose value is returned implicitly, and that its `__name__` is the string `<lambda>` rather than a useful identifier.
The expression-only restriction is not arbitrary. Python separates statements from expressions, and a lambda body sits in an expression slot, so anything that is a statement is a syntax error there: `x = 1`, `return`, `raise`, `for`, `while`, and a bare `pass`. Conditional expressions (`a if cond else b`), comprehensions, boolean operators, and calls are all expressions, so they are fine, which covers most of what a one-line function needs.
Because a lambda is an expression, you can create one exactly where it is consumed, which is why it shows up almost entirely as an argument: `sorted(rows, key=lambda r: r[2])`, `max(items, key=lambda i: i.score)`, `filter(lambda s: s.strip(), lines)`. Like any nested function it closes over variables, not over their values at creation time, so a lambda built inside a loop sees the loop variable's final value unless you freeze it with a default parameter. When you find yourself naming a lambda, or writing one long enough to need a comment, use `def`: you get a real name in tracebacks and room for a docstring.
square = lambda n: n * n
print(square(7))
print(type(square), square.__name__)
pairs = [("pear", 5), ("apple", 12), ("fig", 3)]
print(sorted(pairs, key=lambda item: item[1]))
print((lambda a, b=10: a + b)(5))A lambda is a normal function object whose body is a single expression that is returned implicitly, created inline so it can be handed straight to another function.
Worked examples
Late binding in a loop
Shows that a lambda captures the variable itself, and how a default parameter freezes the current value.
funcs = [lambda x: x * i for i in range(1, 4)]
print([f(10) for f in funcs])
fixed = [lambda x, i=i: x * i for i in range(1, 4)]
print([f(10) for f in fixed])Example explained
Line 1Each lambda in `funcs` looks up `i` when it is called, not when it is created.
Line 2By the time any of them runs, the comprehension has finished and `i` holds 3, so every result is 30.
Line 3In `fixed`, `i=i` evaluates the outer `i` immediately and stores it as a default, giving each lambda its own value.
Line 4The parameter `i` shadows the enclosing `i`, so the body no longer depends on the closure at all.
Expression bodies only
Demonstrates a tuple key built from an expression, and confirms that an assignment in a lambda body is a syntax error.
words = ["bb", "a", "dddd", "ccc"]
print(max(words, key=len))
print(sorted(words, key=lambda w: (len(w) % 2 == 0, w)))
try:
eval("lambda x: x += 1")
except SyntaxError:
print("assignment is a statement, so it cannot be a lambda body")Example explained
Line 1`key=len` needs no lambda: `len` is already the function that computes the value you want.
Line 2The lambda returns a tuple, so odd-length words (`False` sorts before `True`) come first, alphabetically within each group.
Line 3`eval` compiles the string at runtime, which is why the illegal lambda raises `SyntaxError` you can catch instead of killing the file at import time.
Line 4Replacing `x += 1` with the expression `x + 1` would compile fine.
Important notes
A lambda accepts defaults, `*args`, `**kwargs`, and keyword-only parameters, but it cannot carry annotations or a docstring.
Assigning a lambda to a name gives every traceback the label `<lambda>`, which makes errors harder to locate; PEP 8 recommends `def` in that case.
Common mistakes
Writing `lambda n: return n * n` or `lambda: x = 1`; the body is an expression slot, so this is a `SyntaxError` at import time, before any code runs.
Forgetting the parameter in a key function, as in `sorted(words, key=lambda: len(words))`; Python calls it with one argument and raises `TypeError: <lambda>() takes 0 positional arguments but 1 was given`.
Calling instead of passing: `sorted(pairs, key=lambda item: item[1]())` tries to call the integer `5`, raising `TypeError: 'int' object is not callable`.
Try it yourself
Change, predict, then run
Given `people = [{"name": "Ada", "age": 36}, {"name": "Bo", "age": 19}, {"name": "Cy", "age": 36}]`, print the list sorted by descending age and then by name, using a single lambda that returns a tuple key.
Open the Python workspaceCheck your understanding
What does `print([f() for f in [lambda: i for i in range(3)]])` print, and why?
- [0, 1, 2], because each lambda stores the value of i at the moment it was created
- [2, 2, 2], because each lambda looks up i when called, after the loop has finished
- [0, 0, 0], because i is reset to its initial value once the comprehension ends
- A NameError, because i does not exist outside the comprehension
Show answer
The lambdas close over the comprehension's `i` variable, not a snapshot of its value, and none of them runs until the outer comprehension calls them, at which point `i` is 2. Option 1 is tempting because the lambdas are created at three different moments, but creation only records where to find `i`, not what it holds; you would need `lambda i=i: i` to get [0, 1, 2]. There is no NameError either, since the closure keeps the comprehension's cell alive even though `i` is not visible at module level.