Chapter 10

Functional Programming in Python

11.0 Intro · 11.1 Functional Concepts · 11.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.
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

  • A recursive function calls itself.
  • Every recursive function needs a base case.
  • Good fit for problems with repeated self-similar structure.

Recursion is elegant for some problems, but iteration is often simpler in Python.

def factorial(n):
    if n == 0:          # base case
        return 1
    return n * factorial(n - 1)

print(factorial(5))     # 120

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])

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