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.