Skip to main content

Lambdas and Small Callables

A lambda expression creates an anonymous function containing one expression:

by_last_name = lambda person: person.last_name.casefold()
people.sort(key=by_last_name)

Its best use is a short callable passed directly to an API, especially a key= argument. A named function is clearer when logic needs a docstring, type annotations, reuse, multiple steps, or meaningful error traces.

records.sort(key=lambda row: (row.priority, row.created_at))

Prefer comprehensions or generator expressions for ordinary transformation and filtering:

active_names = [user.name for user in users if user.active]
total = sum(order.amount for order in orders)

They usually communicate intent better than nesting map, filter, and lambdas. map remains useful when an existing named callable already expresses the operation.

Closure boundary

Functions close over names, whose values are looked up when the function runs. Creating callbacks in a loop therefore has a late-binding trap:

callbacks = [lambda: index for index in range(3)]
# Every callback returns 2.

Capture the current value explicitly when that is the desired contract:

callbacks = [lambda index=index: index for index in range(3)]

The default argument is evaluated while each function is created. For larger callbacks, a small factory function or functools.partial is easier to explain.

Do not treat lambda syntax as a signal for functional purity. A lambda can call stateful functions and mutate objects just like a named function.

Source