A lambda is a function written as an expression, with no name. It exists for one situation: when you need to hand a small piece of behaviour to something else and giving it a name would be noise.
The syntax
double = lambda n: n * 2
def double_def(n):
return n * 2
print(double(21)) # Output: 42
print(double_def(21)) # Output: 42
lambda, the parameters, a colon, and one expression whose value is
returned. There is no return keyword because there is nothing else it could do.
Those two are the same function. Which is the point of the next section: if you are assigning a
lambda to a name, you have written def the long way round.
Do not assign one to a name
double = lambda n: n * 2 is legal and you should not write it. PEP 8 says so
explicitly, and the reason is practical rather than stylistic — a named def knows its own
name and a lambda does not:
double = lambda n: n * 2
def double_def(n):
return n * 2
print(double.__name__) # Output: <lambda>
print(double_def.__name__) # Output: double_def
That name is what appears in a traceback. Fill a module with lambdas assigned to names and every
stack trace says <lambda>, which is precisely the moment you needed it to say
something else. You also give up docstrings, default arguments read badly, and you cannot put more
than one statement in it anyway.
Where it does belong: a sort key
This is the case that justifies the whole feature.
accounts = [
{"type": "Savings", "balance": 8400.50},
{"type": "Checking", "balance": 1250.00},
{"type": "Credit", "balance": -320.75},
]
for account in sorted(accounts, key=lambda a: a["balance"]):
print(f"{account['type']:<10}{account['balance']:>10,.2f}")
# Output: Credit -320.75
# Output: Checking 1,250.00
# Output: Savings 8,400.50
sorted() calls the key function once per item and sorts by what comes back. Defining
def balance_of(a): return a["balance"] three lines above, using it once, and never
referring to it again would be strictly more to read.
Sort by several things at once by returning a tuple, and negate a number to reverse just that part:
accounts = [
{"type": "Checking", "balance": 90.00},
{"type": "Checking", "balance": 1250.00},
{"type": "Savings", "balance": 8400.50},
]
ordered = sorted(accounts, key=lambda a: (a["type"], -a["balance"]))
for account in ordered:
print(f"{account['type']} {account['balance']:,.2f}")
# Output: Checking 1,250.00
# Output: Checking 90.00
# Output: Savings 8,400.50
In real code
The console bank app that supplies this track's examples uses lambdas exactly this way — as the test handed to a lookup, and as a sort key:
def find_by_account(self, account_id: int, limit: int) -> list[Transaction]:
"""The statement for one account, newest first, at most `limit` lines."""
rows = self.find(lambda t: t.account_id == account_id)
rows.sort(key=lambda t: t.timestamp, reverse=True)
return rows[:limit]
Two lambdas, both one expression, neither given a name. find() takes any function of
one argument that returns a bool; the lambda says which transactions this call wants. That is the
shape to look for.
Other places you will pass one
amounts = [100, -25, 250, -40]
print(max(amounts, key=lambda n: abs(n))) # Output: 250
print(sorted(amounts, key=abs)) # Output: [-25, -40, 100, 250]
print(list(filter(lambda n: n > 0, amounts))) # Output: [100, 250]
print([n for n in amounts if n > 0]) # Output: [100, 250]
Two things worth noticing. key=abs on the second line beats
key=lambda n: abs(n) — if the lambda does nothing but call one function, pass that
function directly. And the last two lines do the same job, which brings us to the rule below.
filter and map: prefer the comprehension
amounts = [100, -25, 250, -40]
print(list(map(lambda n: n * 2, amounts))) # Output: [200, -50, 500, -80]
print([n * 2 for n in amounts]) # Output: [200, -50, 500, -80]
print(list(filter(lambda n: n > 0, amounts))) # Output: [100, 250]
print([n for n in amounts if n > 0]) # Output: [100, 250]
map and filter exist and work. The
comprehension is shorter, does not need the
list() wrapper to be printable, and is what Python programmers read fastest. If you are
writing map or filter with a lambda, the comprehension is almost certainly
the better line.
Where map still wins is when you already have a named function and no lambda is needed
at all — map(str.strip, lines) is clean.
What will not fit in one
A lambda holds one expression, so anything that is a statement is out: no assignment, no
if block, no for, no try, no raise. A conditional
expression is allowed, because it is an expression:
amounts = [100, -25, 250]
label = lambda n: "credit" if n > 0 else "debit"
print([label(n) for n in amounts]) # Output: ['credit', 'debit', 'credit']
That works, and it is also the point at which a def is the better call — it has a name
worth having and somewhere to put a docstring.
The constraint is a feature. When a lambda stops fitting, the language is telling you the logic has outgrown being an anonymous argument.
The loop variable trap
A lambda captures the variable, not its value at the time:
multipliers = []
for n in [1, 2, 3]:
multipliers.append(lambda x: x * n)
assert [m(10) for m in multipliers] == [10, 20, 30], "all three captured the same n"
That assertion fails — every lambda returns 30, because all three closed over the same
n, which finished the loop holding 3. Bind the value with a default
argument, which is evaluated at definition time:
multipliers = []
for n in [1, 2, 3]:
multipliers.append(lambda x, n=n: x * n)
print([m(10) for m in multipliers]) # Output: [10, 20, 30]
Same rule as the mutable default in Functions, seen from the other side: defaults are evaluated once, at definition, and here that is what saves you.
The rule
Use a lambda when it is an argument to another function and fits comfortably on that
line. Anything else — anything you name, anything needing a second line, anything you would
want to test — is a def.
Next: Generators & Iterators.