Lambda Functions

A lambda is a small anonymous function written as a single expression. It exists for the places where naming a function would add nothing.

The syntax

lambda parameters: expression
double = lambda n: n * 2
print(double(5))              # 10

# The same thing with def
def double(n):
    return n * 2
  • No def, no name, no return.
  • The body is a single expression, and its value is returned automatically.
  • It may take any number of parameters, including none.
add = lambda a, b: a + b
greet = lambda name="there": f"Hello, {name}"
constant = lambda: 42

print(add(2, 3), greet(), greet("Meera"), constant())
Assigning a lambda to a name, as above, is legal and is discouraged by PEP 8. If it deserves a name, it deserves a def - which also gives it a proper __name__ for tracebacks. The examples above exist to show the equivalence, not the recommended style.

Where lambdas belong

A lambda earns its place as an argument to a function that expects a small function.

Sorting with a key

people = [
    {"name": "Meera", "age": 27},
    {"name": "Arun", "age": 31},
    {"name": "Sara", "age": 24},
]

print(sorted(people, key=lambda p: p["age"]))
print(sorted(people, key=lambda p: p["name"]))
print(sorted(people, key=lambda p: (-p["age"], p["name"])))

words = ["banana", "kiwi", "apple"]
print(sorted(words, key=lambda w: len(w)))
print(sorted(words, key=lambda w: w[-1]))

pairs = [(1, "b"), (3, "a"), (2, "c")]
print(sorted(pairs, key=lambda pair: pair[1]))

max and min with a key

print(max(people, key=lambda p: p["age"])["name"])       # Arun
print(min(words, key=len))                                # kiwi

scores = {"Meera": 92, "Arun": 78}
print(max(scores, key=lambda name: scores[name]))         # Meera
print(max(scores, key=scores.get))                        # the same, no lambda

map and filter

numbers = [1, 2, 3, 4, 5, 6]

print(list(map(lambda n: n * n, numbers)))                # [1, 4, 9, 16, 25, 36]
print(list(filter(lambda n: n % 2 == 0, numbers)))        # [2, 4, 6]

# A comprehension is usually clearer
print([n * n for n in numbers])
print([n for n in numbers if n % 2 == 0])

In Python, a comprehension normally beats map or filter with a lambda. Reach for map when the function already exists and needs no wrapper: map(str, numbers) or map(str.upper, words).

Grouping and reducing

from functools import reduce

numbers = [1, 2, 3, 4]
print(reduce(lambda a, b: a * b, numbers))          # 24
print(reduce(lambda a, b: a + b, numbers, 100))     # 110, with a starting value

sum, max and min cover most cases without reduce. Use reduce for genuinely custom accumulation, such as a product.

Default behaviour in a dispatch table

operations = {
    "add": lambda a, b: a + b,
    "subtract": lambda a, b: a - b,
    "multiply": lambda a, b: a * b,
    "power": lambda a, b: a ** b,
}

print(operations["multiply"](6, 7))         # 42

for name in sorted(operations):
    print(f"{name:<10}{operations[name](8, 2)}")

This is one of the few places a stored lambda reads better than a def: the whole table is visible in one block.

What a lambda cannot do

# No statements
# f = lambda x: print(x); return x       # SyntaxError
# f = lambda x: if x > 0: "pos"          # SyntaxError
# f = lambda x: for i in range(x): ...   # SyntaxError

# A conditional EXPRESSION is fine, because it is an expression
classify = lambda n: "positive" if n > 0 else "non-positive"
print(classify(5), classify(-5))

# No assignment, except with the walrus operator
# f = lambda x: y = x * 2                # SyntaxError

No if statements, no loops, no try, no assignments, no multiple lines, no docstring, no annotations. If you need any of those, you need def.

The late binding trap

functions = []
for i in range(3):
    functions.append(lambda: i)

print([f() for f in functions])      # [2, 2, 2] - not [0, 1, 2]

The lambda does not capture the value of i; it captures the variable. By the time the functions run, the loop has finished and i is 2. Bind the value with a default argument:

functions = [lambda i=i: i for i in range(3)]
print([f() for f in functions])      # [0, 1, 2]

This applies to every closure, not only lambdas. The advanced functions note covers the mechanism.

Lambda or def?

Use a lambdaUse def
One short expressionAnything needing a statement
Passed directly as an argumentCalled from more than one place
Its purpose is obvious in contextIt needs a name, a docstring or tests
A key= functionLonger than about forty characters
# Fine
sorted(records, key=lambda r: r["date"])

# Not fine - give it a name
sorted(records, key=lambda r: (r["dept"], -r["salary"], r["name"].lower()))


def ranking_key(record):
    """Sort by department, then salary descending, then name."""
    return record["dept"], -record["salary"], record["name"].lower()


sorted(records, key=ranking_key)

Alternatives worth knowing

import operator

pairs = [(1, "b"), (3, "a"), (2, "c")]
print(sorted(pairs, key=operator.itemgetter(1)))       # instead of lambda p: p[1]

people = [{"name": "Meera", "age": 27}]
print(sorted(people, key=operator.itemgetter("age")))

from functools import partial

def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
print(square(7))                                        # 49

Common mistakes

  • Trying to put a statement inside a lambda.
  • Assigning every lambda to a name instead of using def.
  • Writing a lambda so long it needs to be read twice.
  • Falling into the late binding trap in a loop.
  • Using map and filter with a lambda where a comprehension is clearer.
  • Forgetting that map and filter return lazy objects, not lists.

Best practices

  • Use a lambda only as an argument, and keep it to one short expression.
  • Name it with def the moment it needs explaining.
  • Prefer comprehensions to map and filter with lambdas.
  • Use operator.itemgetter and attrgetter for plain field access.
  • Bind loop values with a default argument when creating functions in a loop.

Practice

  1. Sort a list of employee records by department ascending and salary descending, first with a lambda and then with a named function. Say which you prefer.
  2. Build a calculator dispatch table of four lambdas and drive it from user input.
  3. Demonstrate the late binding trap and fix it two different ways.
  4. Rewrite list(map(lambda x: x.strip().lower(), items)) as a comprehension.
  5. Explain why lambda x: x = 5 is a syntax error.

Conclusion

A lambda is one expression, passed somewhere, used immediately. It shines as a key= function and in small dispatch tables. Everywhere else - and always once it needs a name, a docstring or a second line - use def.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →
Python

Scope and the LEGB Rule

Python resolves a name by looking in four places in a fixed order: local, enclosing, global, built-in. Assignment anywhere in a function makes the nam...

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.