Chapter 10

Functional Programming in Python

10.0 Intro · 10.1 Functional Concepts · 10.2 Functional Practice

← → or Space to navigate · F for fullscreen

What is Functional Programming?

Writing programs as transformations from inputs to outputs

Why Functional Programming?

Imperative approach

Describe each step and update state along the way.

prices = [10, 20, 30]
discounted = []

for price in prices:
    new_price = price * 0.9
    discounted.append(new_price)

Functional approach

Describe the transformation and produce a new value.

prices = [10, 20, 30]

discounted = [
    price * 0.9
    for price in prices
]

FP benefits: smaller functions, fewer side effects, easier testing, and clearer data pipelines.

Functional Vocabulary

Pure function Same input gives same output; no external changes.
Immutability Return new values instead of changing old ones.
First-class function Pass functions around like any other value.
Higher-order function Takes a function or returns a function.
Lambda Small anonymous function written inline.
Decorator Wraps a function with reusable behavior.

10.1 Functional Concepts

Purity, immutability, functions as values, lambdas, decorators, comprehensions

Pure Functions

  • Depend only on their arguments.
  • Return a value instead of modifying external state.
  • Easier to test because they are predictable.

Same input → same output. No hidden reads or writes.

# Pure
def add_tax(price, rate):
    return price * (1 + rate)

# Not pure: depends on global state
tax_rate = 0.07

def add_tax_global(price):
    return price * (1 + tax_rate)

Immutability

Functional code often returns a new value instead of changing an existing one.

  • Original data stays available.
  • Unexpected side effects are less likely.
  • Pipelines become easier to reason about.
def add_item(items, new_item):
    return items + [new_item]

cart = ["book", "pen"]
new_cart = add_item(cart, "notebook")

print(cart)      # ['book', 'pen']
print(new_cart)  # ['book', 'pen', 'notebook']

Functions as Values

  • Assign a function to a variable.
  • Pass a function as an argument.
  • Return a function from another function.

This is the foundation for map, filter, decorators, callbacks, and custom sorting.

def square(x):
    return x * x

def apply(func, values):
    return [func(v) for v in values]

nums = [1, 2, 3, 4]
print(apply(square, nums))
# [1, 4, 9, 16]

Lambda Functions

A lambda is a small anonymous function for simple expressions.

Use case Example
Sort key key=lambda s: len(s)
Quick transform lambda x: x * 2
Pair key lambda item: item[1]

If the logic needs multiple steps, use def.

names = ["Ada", "Grace", "Linus"]

by_length = sorted(
    names,
    key=lambda name: len(name)
)

print(by_length)
# ['Ada', 'Grace', 'Linus']

Map, Filter, and Comprehensions

Functional tools

nums = [1, 2, 3, 4, 5]

squares = list(map(lambda n: n*n, nums))
evens   = list(filter(lambda n: n % 2 == 0, nums))

Pythonic alternative

nums = [1, 2, 3, 4, 5]

squares = [n*n for n in nums]
evens   = [n for n in nums if n % 2 == 0]

In Python, comprehensions are often clearer than map and filter with lambdas.

Higher-Order Functions

A higher-order function works with other functions.

  • Takes a function as input.
  • Returns a function as output.
  • Builds reusable behavior without duplicating control flow.
  • make_multiplier returns a closure: multiply remembers factor (see 4.2).
def make_multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(10))  # 20
print(triple(10))  # 30

Decorators

  • A decorator takes a function and returns a wrapped function.
  • Use for logging, timing, validation, caching, and access checks.
  • @decorator syntax replaces manual wrapping.
def announce(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@announce
def greet(name):
    return f"Hello, {name}"

print(greet("Ada"))

10.2 Functional Practice

Recursion, context managers, and practical tools from functools

Recursion: Recap and Trade-offs

Recap from 4.3 Recursion: a base case stops the calls, and a recursive case calls the function on a smaller problem.

  • Fits functional style: repeats work without mutating a loop counter.
  • Good fit for self-similar structure such as trees and nested folders.
Recursion Iteration
Mirrors math definitions Usually faster in Python
Natural for tree traversal No recursion limit
Extra call overhead Uses less memory
Limit near 1000 calls (sys.getrecursionlimit()) Can be harder to read for nested data

lru_cache (later in 10.2) caches a recursive function such as fib.

Context Managers

A context manager handles setup and cleanup around a block of code.

  • Open and close files safely.
  • Acquire and release resources.
  • Keep cleanup reliable even when errors happen.
from contextlib import contextmanager

@contextmanager
def section(name):
    print(f"Start {name}")
    try:
        yield
    finally:
        print(f"End {name}")

with section("demo"):
    print("working")

The functools Module

Tool Use
reduce Combine many values into one
partial Pre-fill some function arguments
lru_cache Memoize expensive function calls

Use these when they simplify the code. Do not force them into every problem.

from functools import reduce, partial, lru_cache

total = reduce(lambda a, b: a + b, [1, 2, 3])

# initializer: needed when items are not the result type
students = [("Alice", 85), ("Bob", 90), ("Charlie", 78)]
grade_sum = reduce(lambda acc, s: acc + s[1], students, 0)   # 253
reduce(lambda acc, x: acc + x, [], 0)                       # 0, no TypeError

def power(base, exp):
    return base ** exp

square = partial(power, exp=2)

@lru_cache
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

Choosing a Style

Use functional style when…

  • You are transforming collections.
  • A pure helper function makes logic easier to test.
  • A comprehension or small pipeline is clearer than a loop.

Prefer another style when…

  • The lambda becomes hard to read.
  • The code needs several statements or error handling.
  • Mutating state is the simplest honest model.

Python supports multiple styles. The goal is readable, testable code — not functional purity.

Chapter 10 — Quick Reference

Concept Key syntax / notes
Pure function No side effects; depends only on arguments
Immutable update Return a new value instead of mutating the original
Lambda lambda args: expression
Map / filter map(func, xs), filter(pred, xs)
Comprehension [expr for item in xs if condition]
Decorator @decorator wraps a function
Recursion Base case plus recursive case
Context manager with manager: handles setup and cleanup
functools reduce, partial, lru_cache

End of Chapter 10

Next: Chapter 11 — Iterators & Generators

iterators · generators · yield · itertools