Python Built-in Higher-Order Functions:
map(), filter(), and reduce()
Table of Contents
- Introduction to Higher-Order Functions
- Understanding Higher-Order Functions
- map() Function
- filter() Function
- reduce() Function
- First-Class Functions
- Inner (Nested) Functions
- Closures
- Real-Life Closure Examples
- Decorators — Concept and Usage
- Decorators with Arguments
- Practical Decorator Use Cases
- Multiple Decorators
- The functools Module
- functools.wraps
- functools.partial
- Anonymous (Lambda) Functions — Advanced Use
- Generators (yield)
- Asynchronous (Async) Functions
- Function Introspection
- Summary Table of Function Concepts
Introduction to Higher-Order Functions
Python is a multi-paradigm language that supports both object-oriented and functional programming styles.
One of the powerful features that come from functional programming is the concept of higher-order functions.
These functions help you write cleaner, shorter, and more expressive code when working with collections like lists, tuples, and sets.
Understanding Higher-Order Functions
A higher-order function is any function that can do one or both of the following:
- Accept another function as an argument, or
- Return another function as a result
In simple terms, higher-order functions treat functions just like data — they can be passed around, stored in variables, or used as inputs to other functions.
This concept makes your code more modular, reusable, and expressive.
Python provides several built-in higher-order functions — among them, map(), filter(), and reduce() are the most widely used.
Example:
def apply_operation(func, x, y):
return func(x, y)
def add(a, b): return a + b
def multiply(a, b): return a * b
print(apply_operation(add, 3, 5))
print(apply_operation(multiply, 3, 5))
Output:
8
151. map() Function
Concept
map() applies a given function to every item of an iterable (like a list or tuple) and returns a map object, which can be converted into a list, set, or tuple.
Syntax
map(function, iterable)
- function: The function to apply to each element.
- iterable: A sequence (like a list) whose items will be processed.
Example 1: Basic Use of map()
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x ** 2, numbers))
print(squares)
Output:
[1, 4, 9, 16, 25]
Explanation:
- The lambda function takes one argument x and returns its square (x ** 2).
- map() applies this function to each element in the list numbers.
Example 2: Using a Defined Function
def to_celsius(fahrenheit):
return (fahrenheit - 32) * 5/9
temps_f = [32, 50, 68, 86]
temps_c = list(map(to_celsius, temps_f))
print(temps_c)
Output:
[0.0, 10.0, 20.0, 30.0]
Here, map() converts each Fahrenheit temperature into Celsius by applying the to_celsius function to each element.
Key Points about map()
- It does not modify the original iterable.
- It returns an iterator, which can be converted to a list or any other sequence type.
- It’s best used when you want to transform all elements of a collection.
2. filter() Function
Concept
filter() filters the elements of an iterable based on a condition provided by a function.
It returns only those elements for which the function returns True.
Syntax
filter(function, iterable)
- function: A function that returns True or False.
- iterable: A sequence to filter.
Example 1: Filtering Even Numbers
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers)
Output:
[2, 4, 6]
Explanation:
- The lambda function checks if each number is divisible by 2.
- filter() keeps only those elements for which the condition is True.
Example 2: Filtering Strings
names = ["John", "Alice", "", "Bob", "", "Clara"]
valid_names = list(filter(lambda name: name != "", names))
print(valid_names)
Output:
['John', 'Alice', 'Bob', 'Clara']
Explanation:
This filters out empty strings, keeping only valid names.
Key Points about filter()
- It returns an iterator (not a list directly).
- If the function returns False, the element is excluded.
- Ideal for selecting specific elements that meet certain conditions.
3. reduce() Function
Concept
reduce() is not a built-in function by default; it’s available in the functools module.
It reduces an iterable into a single cumulative value by applying a function cumulatively to its elements.
Syntax
from functools import reduce
reduce(function, iterable[, initializer])
- function: A function that takes two arguments.
- iterable: Sequence to reduce.
- initializer (optional): Starting value (if provided).
Example 1: Multiplying All Numbers
from functools import reduce
numbers = [1, 2, 3, 4]
product = reduce(lambda x, y: x * y, numbers)
print(product)
Output:
24
Explanation:
- First, 1 * 2 = 2
- Then, 2 * 3 = 6
- Finally, 6 * 4 = 24
reduce() applies the lambda function cumulatively across the list.
Example 2: Summing Numbers with an Initial Value
from functools import reduce
numbers = [10, 20, 30]
total = reduce(lambda x, y: x + y, numbers, 100)
print(total)
Output:
160
Here, the initial value 100 is added before the reduction starts.
Example 3: Finding the Maximum Element
from functools import reduce
numbers = [3, 8, 2, 10, 5]
max_num = reduce(lambda x, y: x if x > y else y, numbers)
print(max_num)
Output:
10
Key Points about reduce()
- Always import it from functools.
- It reduces an iterable into a single result (sum, product, max, etc.).
- Best used when an operation needs to accumulate a single outcome.
Comparison Summary
| Function | Purpose | Input | Output | Typical Use |
|---|---|---|---|---|
| map() | Applies a function to all elements | function + iterable | Transformed iterable | Data transformation |
| filter() | Keeps elements that meet a condition | function + iterable | Filtered iterable | Data selection |
| reduce() | Combines all elements into one | function + iterable | Single value | Aggregation |
Practical Example: Combining All Three
from functools import reduce
numbers = [1, 2, 3, 4, 5, 6]
# Step 1: Square all numbers
squares = list(map(lambda x: x ** 2, numbers))
# Step 2: Filter squares greater than 10
filtered = list(filter(lambda x: x > 10, squares))
# Step 3: Find the sum of remaining numbers
result = reduce(lambda x, y: x + y, filtered)
print("Squares:", squares)
print("Filtered:", filtered)
print("Sum:", result)
Output:
Squares: [1, 4, 9, 16, 25, 36]
Filtered: [16, 25, 36]
Sum: 77
This shows how map(), filter(), and reduce() can be combined for efficient data processing.
Advanced Function Concepts in Python
1. First-Class Functions
In Python, functions are first-class objects.
That means they can be:
- Assigned to variables
- Stored in data structures
- Passed as arguments to other functions
- Returned from other functions
This makes Python functions very flexible.
Example: Assigning and Passing Functions
def greet(name):
return "Hello " + name
say_hello = greet # assign function to variable
print(say_hello("Alice"))
def call_func(func):
print(func("Bob"))
call_func(greet)
Output:
Hello Alice
Hello Bob
Here, greet is treated like any other object — passed and returned freely.
2. Inner (Nested) Functions
Python allows you to define a function inside another function.
This is useful for encapsulating logic that should only be used inside a specific scope.
Example:
def outer_function():
def inner_function():
print("Inner function executed")
inner_function()
outer_function()
Output:
Inner function executed
Here, inner_function() exists only inside outer_function().
3. Closures
A closure is a function that remembers the values of variables from its enclosing scope, even after that scope is gone.
This happens when:
- You define a function inside another function, and
- The inner function refers to variables from the outer function.
Example:
def outer(msg):
def inner():
print("Message:", msg)
return inner # returning function, not calling it
say_hello = outer("Hello World")
say_hello()
Output:
Message: Hello World
Explanation:
- outer() returns inner() function.
- Even though outer() finished execution, inner() still “remembers” msg = "Hello World".
This is a closure — it captures variables from its outer environment.
Real-life Example of a Closure
Closures are often used for configuration or function factories.
def power(exponent):
def raise_to_power(base):
return base ** exponent
return raise_to_power
square = power(2)
cube = power(3)
print(square(5)) # 25
print(cube(5)) # 125
Output:
25
125
Here, square and cube “remember” their exponent values independently.
4. Decorators (In-Depth)
A decorator is a special function that modifies the behavior of another function without changing its source code.
They are built using:
- Higher-order functions (functions returning functions)
- Closures
Syntax:
@decorator_name
def function_name():
...
is equivalent to:
function_name = decorator_name(function_name)
Example 1: Basic Decorator
def decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Before function call
Hello!
After function call
Example 2: Decorator with Arguments
def decorator(func):
def wrapper(*args, **kwargs):
print("Function is being called with arguments:", args)
return func(*args, **kwargs)
return wrapper
@decorator
def add(a, b):
return a + b
print(add(5, 3))
Output:
Function is being called with arguments: (5, 3)
8
Example 3: Practical Use — Logging or Authentication
def login_required(func):
def wrapper(username):
if username == "admin":
return func(username)
else:
print("Access Denied")
return wrapper
@login_required
def dashboard(user):
print(f"Welcome to the dashboard, {user}")
dashboard("guest")
dashboard("admin")
Output:
Access Denied
Welcome to the dashboard, admin
Example 4: Multiple Decorators
You can stack decorators by placing multiple @decorator lines.
def bold(func):
def wrapper():
return "" + func() + ""
return wrapper
def italic(func):
def wrapper():
return "" + func() + ""
return wrapper
@bold
@italic
def greet():
return "Hello"
print(greet())
Output:
Hello
5. The functools Module —
functools is a Python module that provides higher-order function utilities — meaning functions that operate on other functions.
Two of the most important tools:
- functools.wraps – for decorators
- functools.partial – for creating partially applied functions
Both are core functional programming concepts.
(A) @functools.wraps — In-Depth Explanation
Why decorators break metadata?
When you apply a decorator:
@decorator
def say_hi():
...
Python does something like this internally:
say_hi = decorator(say_hi)
Inside the decorator:
def wrapper():
...
return wrapper
So the original function is replaced by wrapper.
Therefore, the decorated function loses:
- __name__
- __doc__
- __module__
- __annotations__
- __signature__
Without wraps, this happens:
def decorator(func):
def wrapper():
func()
return wrapper
@decorator
def say_hi():
"""Says hi."""
print("Hi")
print(say_hi.__name__) # wrapper (incorrect)
print(say_hi.__doc__) # None (lost)
This becomes a big problem for:
- debugging
- introspection
- documentation generators
- IDE autocomplete
- unit test tools
- decorators stacked on decorators
Role of @functools.wraps
wraps is actually a decorator that returns another decorator.
Conceptually:
wraps(original_function) → decorator_that_updates_metadata
This second decorator then applies to the wrapper function.
So:
@wraps(func)
def wrapper():
...
means:
“Copy the metadata of func into this wrapper.”
What metadata does wraps copy?
wraps copies:
- __name__
- __doc__
- __module__
- __annotations__
- __qualname__
- __wrapped__ (very important!)
- Attributes listed in functools.WRAPPER_ASSIGNMENTS
Internally:
wrapper.__wrapped__ = func
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
...
__wrapped__ is especially important because:
- inspect module uses it
- help() uses it
- tools like functools.lru_cache depend on it
Correct decorator with wraps
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper():
print("Wrapped")
return func()
return wrapper
Now metadata is preserved.
Why wraps is essential (real-life reasons)
✔ A. Debugging becomes clearer
Error messages show the real function, not “wrapper”.
✔ B. IDEs and linters identify functions correctly
✔ C. Multiple decorators stack cleanly
Without __wrapped__, stacking decorators gets messy.
✔ D. Tools like Flask, FastAPI depend on metadata
Routing and dependency injection use function names and docs.
✔ E. Testing frameworks like pytest rely on function metadata.
Example: decorator without wraps vs with wraps
Without wraps
def log(func):
def wrapper(*args, **kwargs):
print("Calling", func.__name__)
return func(*args, **kwargs)
return wrapper
@log
def add(a, b):
"""Add two numbers"""
return a + b
Now:
add.__name__ # "wrapper"
add.__doc__ # None
With wraps
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Calling", func.__name__)
return func(*args, **kwargs)
return wrapper
Now:
add.__name__ # "add"
add.__doc__ # "Add two numbers"
(B) functools.partial —
The idea
partial() allows you to fix some arguments in advance and create a new function with fewer required parameters.
This is called:
- partial function application
- argument binding
- currying (related concept)
Basic Example
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
Now:
square(5) → power(5, 2)
cube(4) → power(4, 3)
How partial actually works internally
When you create:
square = partial(power, exponent=2)
partial stores:
func = power
fixed positional args = ()
fixed keyword args = {"exponent": 2}
Calling:
square(10)
is equivalent to:
power(10, exponent=2)
Partial behaves almost like:
def square(x):
return power(x, exponent=2)
But it also supports advanced combinations of positional + keyword arguments.
Inspecting a partial function
print(square.func) # original function
print(square.args) # fixed positional arguments
print(square.keywords) # fixed keyword args
Why use partial?
✔ A. Turn general functions into specialized versions
A very generic function can become many specific functions.
✔ B. Cleaner code
Avoid passing the same parameters repeatedly.
✔ C. Useful for callbacks
GUIs, event handlers, schedulers often need callback functions with zero arguments.
partial allows you to pass extra parameters easily.
✔ D. Useful in functional programming pipelines
6. Anonymous (Lambda) Functions — Advanced Use
Lambdas are short, unnamed functions often used in functional programming style.
They can be used with:
- map()
- filter()
- reduce()
- Sorting with custom keys
Example: Sorting with lambda
students = [("Alice", 25), ("Bob", 20), ("Charlie", 23)]
sorted_students = sorted(students, key=lambda x: x[1])
print(sorted_students)
Output:
[('Bob', 20), ('Charlie', 23), ('Alice', 25)]
7. Generators (Functions that Yield)
A generator is a special kind of function that yields values one at a time, instead of returning them all at once.
They are memory-efficient because they don’t store the entire result in memory.
Example:
def countdown(n):
while n > 0:
yield n
n -= 1
for num in countdown(5):
print(num)
Output:
5
4
3
2
1
Here, yield pauses the function and resumes when next value is requested.
8. Asynchronous (Async) Functions
Python supports asynchronous programming using async and await for non-blocking execution.
Example:
import asyncio
async def greet():
print("Hello")
await asyncio.sleep(1)
print("World")
asyncio.run(greet())
Output:
Hello
World
Here, await pauses execution while waiting for asynchronous tasks to finish, allowing other code to run concurrently.
9. Function Introspection
You can inspect function attributes using built-in attributes like:
- __name__
- __doc__
- __defaults__
- __code__.co_varnames
- __annotations__
Example:
def sample(a: int, b: int = 5) -> int:
"""Adds two numbers"""
return a + b
print(sample.__name__)
print(sample.__doc__)
print(sample.__defaults__)
print(sample.__annotations__)
print(sample.__code__.co_varnames)
Output:
sample
Adds two numbers
(5,)
{'a': , 'b': , 'return': }
('a', 'b')
10. Summary Table
| Concept | Description | Example |
|---|---|---|
| Closure | Inner function remembering variables | Function returning another function |
| Decorator | Modifies another function | @decorator_name |
| Partial | Fix some arguments | partial(func, arg=value) |
| Lambda | Anonymous inline function | lambda x: x + 1 |
| Generator | Function that yields sequence | Uses yield |
| Async Function | Non-blocking async operation | Uses async / await |
