Functional Programming
map, filter, reduce, closures, decorators, partial functions
Python supports functional programming as a first-class paradigm — functions are objects, closures capture scope, decorators transform functions, and higher-order functions like map/filter/reduce are built-in. Mastering these patterns leads to concise, reusable, and testable code.
Key Points
- First-class functions: functions can be assigned to variables, passed as arguments, returned from other functions
- Closures: inner functions capture variables from the enclosing scope even after the outer function returns
- map(fn, iterable): applies fn to each element — returns a lazy iterator; list(map(...)) to evaluate
- filter(pred, iterable): keeps elements where pred returns True — lazy iterator
- functools.reduce(fn, iterable): folds iterable to a single value — left-associative by default
- Decorators: functions that wrap other functions to add behaviour — @functools.wraps preserves metadata
- functools.partial: creates a new function with some arguments pre-filled — currying in Python
- functools.lru_cache / @cache (3.9+): memoisation decorator — caches results of pure functions
- itertools: chain, product, combinations, permutations, groupby, islice, accumulate — lazy combinatoric tools
Python functional: closure factory, parametrized decorator with @wraps, partial, lru_cache, itertools
import functools, itertools, time
# Closure — counter factory
def make_counter(start=0):
count = start
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter(10)
print(c(), c()) # 11, 12
# Decorator — timing + logging
def retry(times=3, delay=1.0):
def decorator(fn):
@functools.wraps(fn) # preserve __name__, __doc__
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return fn(*args, **kwargs)
except Exception as e:
if attempt == times: raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=3, delay=0.5)
def fetch_data(url): ...
# Partial application
from functools import partial
base2 = partial(int, base=2) # int(x, base=2)
print(base2("1010")) # 10
# lru_cache — memoised fibonacci
@functools.lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
# itertools patterns
# Sliding window
def windows(seq, n):
its = [iter(seq)] * n
return zip(*its) # non-overlapping chunks
pairs = list(itertools.combinations(range(5), 2)) # all pairs
matrix = list(itertools.product([0,1], repeat=3)) # 3-bit truth table
# groupby (requires sorted input)
data = sorted(employees, key=lambda e: e.dept)
for dept, members in itertools.groupby(data, key=lambda e: e.dept):
print(dept, list(members))Real-World Example
@lru_cache is the fastest way to add memoisation to a recursive algorithm — it turns exponential Fibonacci into O(n). Decorators are used pervasively in Python frameworks: Flask's @app.route, Django's @login_required, FastAPI's @app.get, Celery's @app.task, pytest's @pytest.fixture — all are function decorators that register or transform the function at definition time.