Difficulty: Intermediate
In Python, functions are first-class objects. This means they can be assigned to variables, stored in data structures like lists and dictionaries, passed as arguments to other functions, and returned as values from functions. This property is the foundation of functional programming in Python and enables powerful patterns like callbacks, strategy dispatch, and function composition.
A higher-order function is any function that takes one or more functions as arguments, returns a function, or both. You have already seen built-in higher-order functions like `map()`, `filter()`, and `sorted()`. Writing your own higher-order functions lets you create abstractions that separate what to do from how to do it. For example, a `retry` function can take any operation as a parameter and handle the retry logic generically.
Passing functions as arguments is one of the most common patterns. When you pass a function to another function, the receiving function can call it whenever needed, with whatever arguments it chooses. This is the callback pattern. In Python, you pass the function object itself (without parentheses); adding parentheses would call it immediately and pass the return value instead.
Returning functions from functions creates what are known as function factories or configurators. The outer function sets up some configuration or state, and the inner function uses that state when called later. This pattern works because of closures -- the returned function retains access to the variables from the enclosing scope. Decorators, one of Python's most distinctive features, are simply higher-order functions that take a function and return a modified version of it.
Function composition is another powerful technique enabled by higher-order functions. You can create a new function that applies multiple transformations in sequence by chaining functions together. While Python does not have a built-in composition operator, you can easily build a `compose` utility that takes multiple functions and returns a single function that applies them in order.
def shout(text):
return text.upper()
def whisper(text):
return text.lower()
# Assign function to a variable
transform = shout
print(transform("hello"))
transform = whisper
print(transform("HELLO"))
# Store functions in a list
operations = [shout, whisper, len]
for op in operations:
print(f"{op.__name__}: {op('Python')}")
Functions can be assigned to variables, stored in lists, and iterated over. The `__name__` attribute reveals the original function name. Notice that `len` is a built-in function but works exactly the same way as user-defined ones -- all functions are first-class.
def apply_operation(func, values):
"""Apply a function to each value and return results."""
return [func(v) for v in values]
numbers = [1, -2, 3, -4, 5]
print(apply_operation(abs, numbers))
print(apply_operation(lambda x: x 2, numbers))
print(apply_operation(str, numbers))
The `apply_operation` function does not know or care what transformation is applied -- it delegates that to the function passed in. This separation of concerns is the core benefit of higher-order functions.
def make_validator(min_len, max_len):
"""Return a function that validates string length."""
def validate(text):
length = len(text)
if length < min_len:
return f"Too short ({length} < {min_len})"
if length > max_len:
return f"Too long ({length} > {max_len})"
return "Valid"
return validate
username_ok = make_validator(3, 20)
password_ok = make_validator(8, 50)
print(username_ok("Al"))
print(username_ok("Alice"))
print(password_ok("short"))
print(password_ok("a_very_secure_password"))
make_validator is a function factory that produces configured validator functions. Each returned function closes over the min_len and max_len values that were passed when it was created.
def compose(*funcs):
"""Compose multiple functions: compose(f, g, h)(x) = f(g(h(x)))."""
from functools import reduce
def composed(x):
return reduce(lambda acc, f: f(acc), reversed(funcs), x)
return composed
double = lambda x: x * 2
add_ten = lambda x: x + 10
square = lambda x: x 2
pipeline = compose(square, add_ten, double)
# double(3) = 6 -> add_ten(6) = 16 -> square(16) = 256
print(pipeline(3))
print(pipeline(5))
print(pipeline(0))
The compose function chains functions right-to-left. For pipeline(3): double(3)=6, add_ten(6)=16, square(16)=256. This pattern lets you build complex transformations from simple, reusable building blocks.
First-Class Functions, Passing Functions as Arguments, Returning Functions