Q1What does this code print?def greet():
print("Hi")
f = greet
f()
Higher-Order Functions — Treating Functions as Arguments and Return Values
Treat Python functions as values — pass them as arguments and return them — all by running examples in your browser.
In the previous article on yield, you saw functions that produce values one at a time. This time you'll keep going on the function side and look at higher-order functions — the mechanism that lets you treat a function itself as a value.
Three patterns to keep in mind: assigning to a variable, passing as an argument (a callback), and returning as a value.
What's a Higher-Order Function — Functions Are Values Too
In Python, functions are values just like integers or strings. You can put them in variables, pass them to other functions as arguments, and return them as results.
A function that takes a function as an argument, or returns a function as a result, is called a higher-order function. Unlike print() or len(), which just operate on values, higher-order functions assemble pieces of behavior.
Assigning a Function to a Variable — Functions Are Referenced Values Too
When you define a function with def, the function body is built in memory and the function name points to that location. Assign with a = print, and a now points to the same function body, so a("HELLO") and print("HELLO") do exactly the same thing.
The trick is to leave off the () after the function name. Add () and you call the function instead, and the return value gets assigned.
a = print # no () — assigns the function body itself to a
a("HELLO") # HELLO ← same as print("HELLO")
print(id(print)) # e.g. 4395020128
print(id(a)) # the same address shows up
# Your own functions work the same way
def greet():
print("Hello")
say = greet # build an alias say
say() # Hello
With or Without () Changes the Meaning
a = print is the form that passes the function, while a = print("HELLO") is the form that calls the function and passes the result. In the second case, print("HELLO")'s return value (None) lands in a, so calling a() raises TypeError: 'NoneType' object is not callable.
Passing a Function as an Argument — Callbacks
The most typical use of higher-order functions is the callback — "a function passed to another function and called at a specific moment", where "the caller decides the actual behavior".
For instance, say you have a routine that "prints a greeting for a list of three names", but you want to swap only the greeting style from the call site. Keep the body as a loop that pulls names one at a time, and take the formatting piece as a function argument — then the caller can reuse the routine just by changing the greeting template.
def greet_all(names, formatter):
for name in names:
print(formatter(name))
def formal(name):
return f"Dear {name}, thank you as always."
def casual(name):
return f"Hey, {name}!"
greet_all(["Alice", "Bob", "Carol"], formal)
# Dear Alice, thank you as always.
# Dear Bob, thank you as always.
# Dear Carol, thank you as always.
greet_all(["Alice", "Bob", "Carol"], casual)
# Hey, Alice!
# Hey, Bob!
# Hey, Carol!
greet_all's body is just a loop; how to turn each name into a greeting line is left to the formatter argument.Branching on Purpose-Specific Callbacks
You're not limited to passing a single callback. "Use this function on success, that one on failure" is another natural fit — anywhere you'd want to pick between multiple callbacks.
For example, when you want to switch behavior based on whether a number is even or odd, the deciding function can do nothing but if-branch, and leave the actual processing to two functions provided by the caller.
def process_number(number, even_callback, odd_callback):
if number % 2 == 0:
even_callback(number)
else:
odd_callback(number)
def handle_even(n):
print(f"{n} is even")
def handle_odd(n):
print(f"{n} is odd")
process_number(4, handle_even, handle_odd) # 4 is even
process_number(7, handle_even, handle_odd) # 7 is odd
Returning a Function as a Value — Closures in Practice
The other higher-order pattern is "returning a function as a value". Returning an inner function with return was covered in the closures article; here, the focus is the practical angle of "handing the caller a function that already remembers its setting".
For example, when you want to mass-produce log functions that stamp the same prefix on every call, set up make_logger("INFO") for the INFO logger and make_logger("ERROR") for the ERROR logger — and the calling code stays as short as info("Process started").
def make_logger(prefix):
def log(message):
print(f"[{prefix}] {message}")
return log # return the function itself
info = make_logger("INFO")
error = make_logger("ERROR")
info("Process started") # [INFO] Process started
error("Connection failed") # [ERROR] Connection failed
info("Process finished") # [INFO] Process finished
- info = make_logger("INFO") — receives a function that remembers INFO
- error = make_logger("ERROR") — receives a separate function that remembers ERROR
- Holds
prefix = "INFO" returns thelogdefined inside
- Prints
[INFO] ...on every call
- Holds
prefix = "ERROR" returns a separatelogfunction
- A separate function object, independent from
info
Knowledge Check
Answer each question one by one.
Q2Which line passes say_hi as a callback?
Q3What does info("OK") print after this code runs?def make_logger(prefix):
def log(message):
print(f"[{prefix}] {message}")
return log
info = make_logger("INFO")