Difficulty: Advanced
A closure is a function that remembers the values from its enclosing scope even after that scope has finished executing. In Python, when a nested function references variables from its parent function, and the parent function returns the nested function, the returned function 'closes over' those variables, keeping them alive. This is the foundation of decorators and many functional patterns.
Closures are created when three conditions are met: there is a nested function, the nested function references a variable from the enclosing function, and the enclosing function returns the nested function. The captured variables are stored in the function's __closure__ attribute as cell objects. Understanding closures is key to understanding how decorators, callbacks, and factory functions work in Python.
The functools module provides essential tools for functional programming. functools.partial creates a new function with some arguments pre-filled, which is useful for adapting functions to different interfaces. functools.reduce applies a two-argument function cumulatively to a sequence, reducing it to a single value -- for example, reduce(operator.add, [1,2,3,4]) computes ((1+2)+3)+4 = 10.
The operator module provides function equivalents of Python's operators: operator.add for +, operator.mul for *, operator.itemgetter for indexing, and operator.attrgetter for attribute access. These are faster than equivalent lambda functions and more readable when passed to higher-order functions like map(), filter(), sorted(), and reduce().
Functional programming patterns in Python include using map() and filter() for transformations, reduce() for aggregation, and combining these with lambda or operator functions for concise data processing pipelines. While Python is not a purely functional language, these tools are powerful when used appropriately alongside imperative code.
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))
print(double(10))
make_multiplier returns multiply, which 'closes over' factor. Each call creates a separate closure with its own factor value.
from functools import partial
def power(base, exponent):
return base exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5))
print(cube(3))
print(square(10))
partial freezes the exponent argument, creating specialized versions of power. This is cleaner than using lambda x: power(x, 2).
from functools import reduce
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda a, b: a + b, numbers)
print(total)
product = reduce(lambda a, b: a * b, numbers)
print(product)
max_val = reduce(lambda a, b: a if a > b else b, numbers)
print(max_val)
reduce applies the function cumulatively: total computes ((((1+2)+3)+4)+5)=15. It reduces a sequence to a single value left to right.
from operator import itemgetter, mul
from functools import reduce
students = [
{"name": "Alice", "grade": 88},
{"name": "Bob", "grade": 95},
{"name": "Charlie", "grade": 82},
]
sorted_students = sorted(students, key=itemgetter("grade"), reverse=True)
for s in sorted_students:
print(f"{s['name']}: {s['grade']}")
factorial_5 = reduce(mul, range(1, 6))
print(f"5! = {factorial_5}")
itemgetter('grade') creates a callable that extracts the 'grade' key -- cleaner and faster than a lambda. operator.mul replaces lambda a,b: a*b for computing factorial.
closures, functools.partial, functools.reduce, operator module, functional patterns