Sorting by key, callbacks and decorators all rely on functions being ordinary values.
In Python, functions are first-class objects: you can assign them to variables, store them in dicts, pass them as arguments and return them from other functions. sorted(people, key=get_age) passes the function get_age itself — note there are no parentheses.
A lambda is a tiny anonymous function limited to a single expression: lambda p: p["age"]. It's ideal for short key= arguments. If you'd want to name it or it grows beyond one simple expression, write a def instead; assigning a lambda to a name gains nothing. The operator module provides ready-made ones like itemgetter("age") and attrgetter("name").
Python resolves names using the LEGB rule: Local, Enclosing function, Global (module), then Built-in. A function defined inside another can read the enclosing function's variables, and it keeps them alive after the outer function returns. That combination — a function plus the variables it captured — is a closure:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return incrementAssigning to a name makes it local by default, so to rebind a captured variable you declare it nonlocal (or global for a module variable).
A classic surprise: closures capture variables, not values. Lambdas created in a loop all see the loop variable's final value. Bind the current value with a default argument (lambda x=x: ...) or use functools.partial.
Example
from functools import partial
from operator import itemgetter
people = [{"name": "Ana", "age": 31}, {"name": "Ben", "age": 27}]
print([p["name"] for p in sorted(people, key=lambda p: p["age"])])
# ['Ben', 'Ana']
print(max(people, key=itemgetter("age"))["name"]) # Ana
ops = {"add": lambda a, b: a + b, "mul": lambda a, b: a * b}
print(ops["mul"](6, 7)) # 42
def make_multiplier(factor):
def multiply(x):
return x * factor # captures factor
return multiply
print(make_multiplier(3)(5)) # 15
late = [lambda: i for i in range(3)]
print([f() for f in late]) # [2, 2, 2]
bound = [lambda i=i: i for i in range(3)]
print([f() for f in bound]) # [0, 1, 2]
int_base2 = partial(int, base=2)
print(int_base2("1010")) # 10Remember
- Functions are values: pass them without calling them
- Use
lambdaonly for short single expressions like sort keys - Closures capture variables, not values; use
nonlocalto rebind them
Watch out for
- Writing
key=get_age()(calling it) instead ofkey=get_age. - Expecting loop-created lambdas to remember each iteration's value.