Lambdas & Higher-Order Functions
A higher-order function takes other functions as arguments or returns one. Python's built-ins sorted, min, max, map and filter all work this way, and lambda lets you write the small functions they need right where you use them.
Lambda expressions
square = lambda n: n * n add = lambda a, b: a + b print(square(6), add(2, 3)) def square_def(n): return n * n print(square_def(6))
36 5 36
lambda params: expression creates a small anonymous function whose body is a single expression, automatically returned. Assigning a lambda to a name, as on the first line, is discouraged by style guides — use def for named functions. Lambdas shine as arguments.
Sorting with key
people = [("Ana", 31), ("Ben", 25), ("Cleo", 31), ("Dev", 19)] print(sorted(people, key=lambda p: p[1])) print(sorted(people, key=lambda p: (-p[1], p[0]))) print(max(people, key=lambda p: p[1])) print(min(["banana", "kiwi", "apple"], key=len))
[('Dev', 19), ('Ben', 25), ('Ana', 31), ('Cleo', 31)]
[('Ana', 31), ('Cleo', 31), ('Ben', 25), ('Dev', 19)]
('Ana', 31)
kiwiThe key function is called once per item to produce the value that's compared. Returning a tuple sorts by several criteria; negating a number reverses that one criterion.
map and filter
prices = [12.5, 7.0, 30.0, 4.25] with_tax = list(map(lambda p: round(p * 1.2, 2), prices)) cheap = list(filter(lambda p: p < 10, prices)) as_text = list(map(str, prices)) print(with_tax) print(cheap) print(as_text)
[15.0, 8.4, 36.0, 5.1] [7.0, 4.25] ['12.5', '7.0', '30.0', '4.25']
map(fn, items) applies a function to each item; filter(fn, items) keeps items where it returns truthy. Both return lazy iterators, so wrap them in list() to see the results. In most Python code, comprehensions are preferred because they read more naturally:
prices = [12.5, 7.0, 30.0, 4.25] print([round(p * 1.2, 2) for p in prices]) print([p for p in prices if p < 10])
[15.0, 8.4, 36.0, 5.1] [7.0, 4.25]
map remains neat when you already have a named function: map(str, numbers), map(int, line.split()).
Functions that return functions
def multiplier(factor): return lambda n: n * factor def greater_than(limit): def test(n): return n > limit return test double = multiplier(2) over_ten = greater_than(10) print(double(21)) print(list(filter(over_ten, [5, 12, 9, 30])))
42 [12, 30]
Each returned function is a closure, remembering the factor or limit it was created with.
functools helpers
from functools import partial, reduce from operator import mul, itemgetter, attrgetter power_of_two = partial(pow, 2) print(power_of_two(10)) print(reduce(mul, [1, 2, 3, 4, 5])) rows = [{"name": "Ana", "score": 91}, {"name": "Ben", "score": 78}] print(sorted(rows, key=itemgetter("score"))) print(sorted(["b", "A", "c"], key=str.lower))
1024
120
[{'name': 'Ben', 'score': 78}, {'name': 'Ana', 'score': 91}]
['A', 'b', 'c']partialcreates a new function with some arguments pre-filled.reducecombines items pairwise into a single value. (Built-ins likesum,maxandmath.prodare usually clearer.)operator.itemgetterandattrgetterare fast, readable replacements for lambdas likelambda d: d["score"].
Lesson 51 explores functools and itertools further.
Callbacks
Passing a function to be called later is the "callback" pattern, used in GUIs, web frameworks and event systems:
def process(items, on_item, on_done): for item in items: on_item(item) on_done(len(items)) process( ["a.txt", "b.txt"], on_item=lambda name: print("processing", name), on_done=lambda n: print(f"finished {n} files"), )
processing a.txt processing b.txt finished 2 files
Lambda limitations
- Only one expression — no statements, loops,
tryblocks or assignments (except:=). - No name in tracebacks: errors show
<lambda>, which is harder to debug. - If a lambda gets long or is used in more than one place, turn it into a
def.