Higher-Order Functions: Functions That Receive and Return Functions
- Explain what a Higher-Order Function is and why it is possible in Python
- Pass a function as a parameter — without brackets — and call it from inside another function
- Use map, filter and the key parameter of sorted, max and min
- Write a function that returns a function
What will we learn?
- What a Higher-Order Function is.
- How to pass a function as a parameter — and why without brackets.
- The built-in functions
mapandfilter. - The
keyparameter ofsorted,maxandmin. - A function that returns a function.
Why do we need this?
Two almost identical functions:
def double_all(numbers):
result = []
for n in numbers:
result.append(n * 2)
return result
def square_all(numbers):
result = []
for n in numbers:
result.append(n * n)
return resultThe loop is identical — only the action on each item is different. Every new action (half, plus 10, rounding…) requires another whole function, and that is exactly the duplication DRY forbids.
The idea: write the loop once, and pass the action itself as a parameter.
Explanation from the basics
A function is an object
We already saw in the lesson “functions in depth” that in Python a function is an object — like a number or a list:
double(without brackets) — the function itself. You can store it in a variable or pass it on.double(5)(with brackets) — a call to the function. The result is what it returns.
Higher-Order Function
A Higher-Order Function is a function that:
- Receives another function as a parameter, or
- Returns a function.
You have already used one: sorted(products, key=lambda p: p[1]) — sorted receives a function in the key parameter.
A washing machine knows how to “go over all the clothes”. What to do to each piece — that you choose with the program: delicate wash, regular or drying. You don’t buy a new machine for every program — you give it the program. The Higher-Order Function is the machine, and the function you pass is the program.
Syntax
def apply_to_all(numbers, action): # action is a function
result = []
for n in numbers:
result.append(action(n)) # call it for each item
return result
apply_to_all([1, 2, 3], double) # pass it: no parentheses
list(map(double, [1, 2, 3])) # built-in: map
list(filter(is_even, [1, 2, 3, 4])) # built-in: filter
sorted(words, key=len) # key: sort by the result of lenFirst code example
week-2/apply_to_all.py:
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))Output:
[2, 4, 6]
[1, 4, 9]
[2, 2]Breaking down the code line by line
| Line | Explanation |
|---|---|
def apply_to_all(numbers, action): | action is a regular parameter — only we expect it to hold a function. |
result.append(action(n)) | We call the function we received, on every item. |
apply_to_all([1, 2, 3], double) | We pass double without brackets — the function, not its result. |
lambda n: n * n | A short function without a name, created on the spot. |
round | A built-in function can be passed too. |
Another example
map, filter and key. week-2/hof_builtins.py:
prices = [100, 250, 40, 80]
with_fee = list(map(lambda p: p + 10, prices))
cheap = list(filter(lambda p: p < 100, prices))
print(with_fee)
print(cheap)
words = ["banana", "fig", "cherry", "kiwi"]
print(sorted(words, key=len))
print(max(words, key=len))
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(max(students, key=lambda s: s[1]))Output:
[110, 260, 50, 90]
[40, 80]
['fig', 'kiwi', 'banana', 'cherry']
banana
('Dana', 92)map(func, items)— runsfuncon every item.list(...)turns the result into a list.filter(func, items)— keeps only the items for whichfuncreturnsTrue.key=len—sortedandmaxcompare by the result oflenon each word, not by the word itself.
A function that returns a function. week-2/make_multiplier.py:
def make_multiplier(factor):
def multiply(n):
return n * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))
print(triple(5))Output:
10
15make_multiplier is a “factory” that produces functions: every call returns a new function, which remembers its own factor. How exactly it remembers — we will see in the next lesson, on nested functions.
Common mistake
The mistake: calling the function instead of passing it.
apply_to_all([1, 2], double())TypeError: double() missing 1 required positional argument: 'n'Why? double() with brackets calls the function immediately — without a parameter — so it fails even before apply_to_all starts. Pass the function without brackets: apply_to_all([1, 2], double). The function that receives it is the one that will call it.
Practice
The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.
Exercise 1: A function as a parameter
Write a function apply_to_all(numbers, action) that runs action on every number and returns a new list. Use it with a regular function double, with a lambda that squares, and with round.
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))Expected output:
[2, 4, 6]
[1, 4, 9]
[2, 2]Show solution
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda n: n * n))
print(apply_to_all([1.6, 2.4], round))[2, 4, 6]
[1, 4, 9]
[2, 2]Pass the function without brackets (double, round). apply_to_all is the one that calls it.
Exercise 2: map and filter
prices = [20, 75, 120, 45, 90]- Keep only prices above 50, using
filter. - Add 10 to every price that remained, using
map.
Expected output:
[75, 120, 90]
[85, 130, 100]Show solution
prices = [20, 75, 120, 45, 90]
expensive = list(filter(lambda p: p > 50, prices))
raised = list(map(lambda p: p + 10, expensive))
print(expensive)
print(raised)[75, 120, 90]
[85, 130, 100]filter keeps only what the lambda returns True for. map runs the lambda on every item. list(...) turns the result into a list.
Exercise 3: key — what to sort by
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]- Sort the words by length.
- Find the longest word.
- Sort the students by grade, from the highest to the lowest, and print only the names.
Expected output:
['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
AviShow solution
words = ["banana", "fig", "cherry", "kiwi"]
students = [("Dana", 92), ("Avi", 78), ("Noa", 85)]
print(sorted(words, key=len))
print(max(words, key=len))
for name, grade in sorted(students, key=lambda s: s[1], reverse=True):
print(name)['fig', 'kiwi', 'banana', 'cherry']
banana
Dana
Noa
Avikey=len — compare by the result of len on each word, not by the word itself.
Exercise 4: A function that returns a function
Write a function make_discount(percent) that returns a function, which receives a price and returns it after the discount.
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))Expected output:
180.0
100.0Show solution
def make_discount(percent):
def apply(price):
return price - price * percent / 100
return apply
ten_off = make_discount(10)
half_price = make_discount(50)
print(ten_off(200))
print(half_price(200))180.0
100.0make_discount is a “factory” — every call produces a new function that remembers its own percent.
Exercise 5: Calling instead of passing
def double(n):
return n * 2
print(apply_to_all([1, 2], double()))- Which error will you get, and why?
- What is the fix?
Show solution
TypeError: double() missing 1 required positional argument: 'n'. The brackets indouble()call the function immediately — without a parameter — even beforeapply_to_allstarts.- Pass the function itself, without brackets:
def apply_to_all(numbers, action):
result = []
for n in numbers:
result.append(action(n))
return result
def double(n):
return n * 2
print(apply_to_all([1, 2], double))[2, 4]Summary
- Higher-Order Function = a function that receives a function or returns a function. It is possible because a function is an object.
- Pass a function without brackets — the function that receives it is the one that calls it.
map— an action on every item.filter— keeps what meets the condition.key=insorted,max,min— sets what to compare by.- A function can return a function — a “factory” that produces customized functions.