Advanced Lesson
Functional Programming Techniques in Python
Master functional programming patterns, pure functions, composition, and advanced FP architectures
What Functional Programming Actually Means
Functional programming is based on core principles:
| Principle | What It Means | Real-World Analogy |
|---|---|---|
| Pure Functions | Same input → same output, no side effects | A calculator: 2+2 always equals 4 |
| Immutability | Data never changes; create new copies | Editing a photo creates a new file |
| First-Class Functions | Functions can be passed around like data | Giving someone a recipe card to follow |
| Higher-Order Functions | Functions that work with other functions | A chef who teaches other chefs techniques |
| Declarative Style | Describe what, not how | "Make me a sandwich" vs step-by-step instructions |
- Functions are first-class citizens — can be stored, passed, returned
- No mutating state — create new data instead of modifying
- Pure functions — no side effects, predictable output
- Immutability — treat data as unchangeable
- Declarative style — describe what you want, not how
Pure Functions
A pure function has no side effects, doesn't modify external state, and always produces the same output for the same input.
Pure vs Impure Functions
Understand the difference between pure and impure functions
# Pure function - predictable, no side effects
def pure_add(x, y):
return x + y
print(pure_add(2, 3)) # Always returns 5
print(pure_add(2, 3)) # Still returns 5
# Impure function - has side effects
count = 0
def impure_increment():
global count
count += 1
return count
print(impure_increment()) # 1
print(impure_increment()) # 2 (different result!)
# Pure functions are:
# ✓ easier to test
# ✓ easier to debug
# ✓ safer to parallelize
# ✓ more reliableImmutability in Python
FP encourages transforming data instead of mutating it. Use immutable types and return new values.
Immutability in Python
Use immutable data structures for safer functional code
# Immutable types: int, float, str, tuple, frozenset
# Mutable types: list, dict, set
# FP style - transform, don't mutate
nums = [1, 2, 3, 4, 5]
# Good FP style - create new list
doubled = [x * 2 for x in nums]
print(f"Original: {nums}")
print(f"Doubled: {doubled}")
# Avoid mutation (when following FP)
# nums.append(6) # modifies original
# Using tuples for immutability
point = (3, 4)
# point[0] = 5 # Error! Tuples are immutable
# Frozen sets
fs = frozenset([1, 2, 3])
# fs.add(4) # Erro
...Higher-Order Functions
Functions that take functions as arguments or return functions. Core to functional programming.
Higher-Order Functions
Functions that take or return other functions
# Higher-order function - takes function as argument
def apply_twice(func, value):
return func(func(value))
def add_one(x):
return x + 1
def double(x):
return x * 2
result1 = apply_twice(add_one, 5)
print(f"Apply add_one twice to 5: {result1}") # 7
result2 = apply_twice(double, 3)
print(f"Apply double twice to 3: {result2}") # 12
# Higher-order function - returns a function
def make_multiplier(n):
def multiplier(x):
return x * n
return multiplier
times_3 = ma
...map(), filter(), reduce() — Core FP Tools
The fundamental functional transformations for working with sequences.
map, filter, reduce
Core functional transformations for sequences
from functools import reduce
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# map() - transform each element
doubled = list(map(lambda x: x * 2, nums))
print(f"Doubled: {doubled}")
# filter() - keep only matching items
evens = list(filter(lambda x: x % 2 == 0, nums))
print(f"Evens: {evens}")
# reduce() - combine into single value
total = reduce(lambda a, b: a + b, nums)
print(f"Sum: {total}")
product = reduce(lambda a, b: a * b, nums)
print(f"Product: {product}")
# Chaining operations
result = lis
...Lambda Functions
Anonymous functions perfect for functional pipelines and quick transformations.
Lambda Functions
Anonymous functions for functional pipelines
# Basic lambda syntax
square = lambda x: x * x
print(f"Square of 5: {square(5)}")
# Lambdas with multiple arguments
add = lambda x, y: x + y
print(f"3 + 4 = {add(3, 4)}")
# Lambdas in sorting
users = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Charlie", "age": 35}
]
sorted_by_age = sorted(users, key=lambda u: u["age"])
print("\nSorted by age:")
for user in sorted_by_age:
print(f" {user['name']}: {user['age']}")
# Lambdas in functional chains
nums =
...Function Composition
Building complex operations by chaining simple pure functions.
Function Composition
Build complex operations by chaining simple functions
# Composition utility
def compose(*funcs):
def wrapper(x):
for f in reversed(funcs):
x = f(x)
return x
return wrapper
# Simple functions
double = lambda x: x * 2
increment = lambda x: x + 1
square = lambda x: x * x
# Compose them
f1 = compose(increment, double)
print(f"Double then increment 5: {f1(5)}") # 11
f2 = compose(square, increment, double)
print(f"Double, increment, square 3: {f2(3)}") # 49
# String processing pipeline
clean = compose(
str.st
...functools.partial — Pre-Configuring Functions
Create specialized versions of functions by "freezing" some arguments.
functools.partial
Pre-configure functions by freezing arguments
from functools import partial
import operator
# Basic partial application
def power(base, exp):
return base ** exp
square = partial(power, exp=2)
cube = partial(power, exp=3)
print(f"5² = {square(5)}")
print(f"5³ = {cube(5)}")
# With operators
times_10 = partial(operator.mul, 10)
result = list(map(times_10, [1, 2, 3, 4, 5]))
print(f"\nMultiply by 10: {result}")
# Practical example: logging with context
def log_message(level, message, context=""):
print(f"[{level}] {message} {context
...Generator-Based Functional Programming
Generators provide lazy evaluation and memory-efficient functional pipelines.
Generator-Based FP
Lazy evaluation with generator pipelines
# Generator pipeline for processing data
def numbers(n):
"""Generate numbers"""
for i in range(n):
yield i
def only_evens(nums):
"""Filter to only evens"""
for n in nums:
if n % 2 == 0:
yield n
def squared(nums):
"""Square each number"""
for n in nums:
yield n ** 2
# Chain generators (lazy evaluation)
pipeline = squared(only_evens(numbers(10)))
print("Processing numbers 0-9:")
for result in pipeline:
print(f" {result}")
# File
...Advanced: Currying
Transform multi-argument functions into chains of single-argument functions.
Currying
Transform multi-arg functions into chains
# Manual currying
def curry_add(a):
def add_b(b):
def add_c(c):
return a + b + c
return add_c
return add_b
result = curry_add(1)(2)(3)
print(f"Curried add: {result}")
# Generic curry decorator
def curry(func):
"""Transform f(a, b, c) into f(a)(b)(c)"""
def curried(a):
return lambda b: lambda c: func(a, b, c)
return curried
@curry
def multiply(a, b, c):
return a * b * c
result = multiply(2)(3)(4)
print(f"Curried multiply: {result}")
...Decorators — Functional Power Tools
Decorators are higher-order functions that transform and enhance functions.
Decorators as FP
Higher-order functions that transform functions
from functools import wraps
import time
# Simple logging decorator
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f" → {result}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
print("Example 1: Logging")
add(3, 5)
add(10, 20)
# Timing decorator
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
...Functional Error Handling
Handle errors with values instead of exceptions, enabling safer composition.
Functional Error Handling
Handle errors with values, not exceptions
# Option/Maybe pattern
class Maybe:
def __init__(self, value):
self.value = value
def bind(self, func):
if self.value is None:
return Maybe(None)
try:
return Maybe(func(self.value))
except:
return Maybe(None)
def get_or(self, default):
return self.value if self.value is not None else default
# Safe operations
def safe_divide(x):
return 100 / x if x != 0 else None
def double(x):
return x *
...Immutable Data Structures
Use frozen dataclasses and immutable collections for safer functional code.
Immutable Data Structures
Frozen dataclasses and immutable collections
from dataclasses import dataclass
from typing import FrozenSet
# Frozen dataclass (immutable)
@dataclass(frozen=True)
class Point:
x: int
y: int
def move(self, dx, dy):
"""Returns new Point instead of modifying"""
return Point(self.x + dx, self.y + dy)
p1 = Point(0, 0)
p2 = p1.move(3, 4)
print(f"Original: {p1}")
print(f"Moved: {p2}")
# p1.x = 10 # Error! Frozen dataclass is immutable
# Frozen dataclass with methods
@dataclass(frozen=True)
class User:
id:
...Real-World Functional Pipeline
A complete functional architecture for data processing.
Real-World Functional Pipeline
Complete functional data processing architecture
from functools import reduce
# Pure transformation functions
def parse_line(line):
"""Parse CSV-like line"""
parts = line.strip().split(",")
return {"name": parts[0], "value": int(parts[1])}
def is_valid(record):
"""Check if record is valid"""
return record["value"] > 0
def extract_value(record):
"""Extract just the value"""
return record["value"]
def sum_values(acc, value):
"""Accumulator for reduce"""
return acc + value
# Sample data
data = """Alice,100
...Summary
You've learned comprehensive functional programming in Python:
- Pure functions and immutability principles
- Higher-order functions and function composition
- map(), filter(), reduce() and lambda functions
- functools utilities (partial, lru_cache, reduce)
- Generator-based lazy evaluation
- Advanced patterns: currying, decorators, monads
- Functional error handling with Maybe/Result
- Immutable data structures
- Real-world functional pipelines
Functional programming helps you build cleaner, more predictable, and easier-to-maintain code. These patterns are essential for data engineering, ML pipelines, ETL systems, and modern backend architectures.
📋 Quick Reference — Functional Programming
| Tool / Syntax | What it does |
|---|---|
| map(fn, iterable) | Apply fn to every element |
| filter(fn, iterable) | Keep elements where fn returns True |
| functools.reduce(fn, iterable) | Accumulate into a single value |
| lambda x: x * 2 | Anonymous inline function |
| functools.partial(fn, arg) | Pre-fill some function arguments |
🎉 Great work! You've completed this lesson.
You can now write purely functional Python using map, filter, reduce, and composition — essential for data pipelines and ML workflows.
Up next: Metaprogramming — write code that generates and inspects other code at runtime.
Sign up for free to track which lessons you've completed and get learning reminders.