Python Lambda Functions: Sort Keys, Map, Filter and Reduce
A Python lambda is an inline one-expression function. How key functions sort by any criterion, map and filter versus comprehensions, partial and reduce.
- Course: Python study plan
- Module: Functions
- Kind: Lesson
- Reading time: 13 min
- Runtime: CPython 3.11
What is a lambda function in Python?
A lambda in Python is an anonymous function written as lambda params: expression, whose body is a single expression that is returned automatically. It cannot contain statements, assignments or loops, though a conditional expression is fine. Lambdas belong inline as arguments, such as sorted(pairs, key=lambda p: p[1]); a function worth naming should be a def, which gives tracebacks a real name.
Lesson
A higher-order function takes a function as an argument or returns one, and Python is full of them: sorted(key=…), max(key=…), map, filter, functools.reduce, functools.partial, and every callback and decorator you will meet. A lambda is the anonymous one-expression function you hand to them when a def would be ceremony. This lesson covers the lambda syntax and its single limitation, the key-function idiom that sorts and selects by any criterion, map/filter and when a comprehension beats them, partial for fixing arguments, reduce for folds, and the operator module that names every operator as a function.
lambda
square = lambda x: x * x # equivalent to def square(x): return x * x
print(square(4)) # 16
lambda params: expression builds a function object whose body is a single expression — no statements, no return (the expression's value is returned), no assignment, no loops, no if statement (the conditional expression is fine). It can take defaults, *args and **kwargs like any function. Its __name__ is '<lambda>', which is the reason not to assign one to a name as above: def gives the function a real name for tracebacks and help, at the cost of one more line. A lambda belongs inline, as an argument, where the reader sees it and its use together.
Key functions
Every ordering built-in takes key=, a one-argument function applied to each element to produce the value that is actually compared:
words = ["banana", "Apple", "cherry"]
sorted(words) # ['Apple', 'banana', 'cherry'] — uppercase sorts first
sorted(words, key=str.lower) # ['Apple', 'banana', 'cherry'] — case-insensitive
sorted(words, key=len) # ['Apple', 'banana', 'cherry'] — by length (stable on ties)
max(words, key=len) # 'banana'
sorted(pairs, key=lambda p: (-p[1], p[0])) # by second value descending, then first ascending
The key is computed once per element (not on every comparison), so an expensive key is fine. Returning a tuple sorts by several criteria in order; negating a numeric component reverses just that criterion; reverse=True reverses the whole order. Any callable works as a key — a built-in (len, str.lower, abs), a lambda, a def, or the operator helpers below.
map and filter
map(f, iterable) applies f to each element lazily; filter(pred, iterable) keeps the elements for which pred is true. Both return iterators, so wrap in list() to see them:
list(map(int, ["1", "2", "3"])) # [1, 2, 3]
list(filter(str.isdigit, ["1", "a", "22"])) # ['1', '22']
list(map(lambda x, y: x + y, [1, 2], [10, 20])) # [11, 22] — map takes several iterables
map(int, tokens) and filter(None, xs) (drops falsy values) are the idiomatic uses: an existing function applied to everything. The moment you write map(lambda x: …) or filter(lambda x: …), a comprehension says the same thing more readably — [x * 2 for x in xs], [x for x in xs if x > 0] — and can combine both (Module 6). Choose map when the function already exists; choose the comprehension when you would have to write a lambda.
functools.partial
partial(f, *args, **kwargs) returns a new function with some of f's arguments fixed:
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5), cube(2)) # 25 8
int_from_binary = partial(int, base=2)
list(map(int_from_binary, ["101", "11"])) # [5, 3]
It replaces lambda x: power(x, 2), and unlike the lambda it carries its function and fixed arguments as inspectable attributes (square.func, square.keywords). It is also the cleanest fix for late binding in loops (Module 4 lesson 3), because the argument is bound at the moment partial is called.
functools.reduce
reduce(f, iterable, initial) folds a sequence to one value by applying f(accumulator, element) left to right:
from functools import reduce
import operator
reduce(operator.mul, [1, 2, 3, 4], 1) # 24 — the product
reduce(lambda a, b: a if len(a) >= len(b) else b, words) # the longest word (first on ties)
Python deliberately left reduce out of the built-ins: sum, min, max, any, all, math.prod and "".join cover the common folds by name, and a for loop with an accumulator is clearer than a reduce with a lambda. Reach for it when the combining function is a named function and the fold has no built-in.
The operator module
operator.add, mul, neg, lt, eq, contains, getitem and the rest are the operators as functions — the arguments reduce, map and sorted want when a lambda would only wrap an operator. Three factories are used constantly with key=:
from operator import itemgetter, attrgetter, methodcaller
sorted(rows, key=itemgetter(2)) # by column 2
sorted(rows, key=itemgetter(1, 0)) # by column 1, then 0 — returns a tuple
sorted(people, key=attrgetter("age")) # by an attribute
sorted(words, key=methodcaller("lower")) # by calling a method
itemgetter(1) is lambda x: x[1] with a name and a small speed advantage.
Functions returning functions
The factory pattern — a function that builds and returns a specialised function — is the closure of the previous lesson put to work:
def make_validator(lo, hi):
def valid(x):
return lo <= x <= hi
return valid
in_percent = make_validator(0, 100)
print(in_percent(50), in_percent(150)) # True False
Decorators (Module 16) are exactly this with the function itself as the input.
Pitfalls
- A lambda that needs a statement — it cannot; write a
def. - Assigning a lambda to a name instead of using
def. - Forgetting
list()aroundmap/filterand printing<map object at …>; or iterating one twice (it is exhausted after the first pass). key=len()— calling instead of passing;key=len.reducewith a lambda wheresumor a loop is clearer.sorted(xs, key=lambda x: -x)on strings — negation needs numbers; usereverse=True.
Key takeaways
lambda params: expressionis an inline single-expression function; usedeffor anything named or multi-line.key=onsorted/min/maxselects the comparison value; tuples sort by several criteria, negation reverses one.map/filterapply existing functions lazily; prefer a comprehension when you would write a lambda.partialfixes arguments;reducefolds with a named function;operatornames the operators anditemgetter/attrgettermake keys.- A function returning a function is a closure factory — the shape decorators build on.
Common questions
How do I sort a list with a lambda in Python?
Pass it as the key: sorted(pairs, key=lambda p: p[1]) sorts by each element's second item. Return a tuple to sort by several criteria in order, and negate a numeric part to reverse just that one: key=lambda p: (-p[1], p[0]) sorts by the second value descending, then the first ascending.
Should I use map and filter or a list comprehension in Python?
Use map or filter when the function already exists, as in map(int, tokens) or filter(None, xs). The moment you would write a lambda, a comprehension is clearer: [x * 2 for x in xs] and [x for x in xs if x > 0]. map and filter return lazy iterators, so wrap them in list() to see the values.
What does functools.partial do in Python?
partial(f, *args, **kwargs) returns a new function with some of f's arguments fixed, as in square = partial(power, exponent=2) or partial(int, base=2). It replaces a lambda that only wraps a call, keeps its function and arguments inspectable, and binds values immediately, which fixes late binding in loops.
What does reduce do in Python?
functools.reduce(f, iterable, initial) folds a sequence into one value by applying f(accumulator, element) from left to right: reduce(operator.mul, [1, 2, 3, 4], 1) is 24. It is not a built-in on purpose — sum, min, max, math.prod and join cover the common folds, and a loop is often clearer.
Exercises
Sort by several keys
Read n records name score time and sort them by score descending, then by time ascending, then by name ascending, using a single sorted call with a key function returning a tuple. Then print the best record by the same ordering using min with the same key.
Input: n, then n lines. Output: the sorted records as name score time, then best: <name>.
3
ada 90 12
bob 90 10
cy 80 5
prints
bob 90 10
ada 90 12
cy 80 5
best: bobA pipeline from partial
The first line lists operations as name value pairs: add k, sub k (subtract k), mul k, pow k. Build each step as a functools.partial of the matching two-argument helper with k fixed, then apply the steps in order to every integer on the second line using map and print the results.
Input: a line of operation pairs (possibly empty), then a line of integers. Output: the results, space-separated.
add 3 mul 2
1 2 3
prints
8 10 12In this module: Functions
- Defining functions — def, return and functions as values
- Parameters and arguments — positional, keyword, defaults, *args and **kwargs
- Scope and closures — LEGB, global, nonlocal and late binding
- Recursion — base cases, the call stack and memoisation
- Lambdas and higher-order functions (this lesson)
- Type hints and docstrings — the contract a function publishes
- Checkpoint — Functions
← Recursion — base cases, the call stack and memoisation · Type hints and docstrings — the contract a function publishes →