What is Functional Programming?
Functional programming (FP) is a programming paradigm that treats computation as the evaluation of mathematical functions. It emphasizes immutability, pure functions, and avoiding side effects.
While Python is not a purely functional language, it supports many functional programming concepts that can make your code cleaner and more predictable.
Core Concepts
- Pure Functions: Same input always gives same output, no side effects
- Immutability: Don't modify data, create new data instead
- First-Class Functions: Functions can be passed around like data
- Higher-Order Functions: Functions that take or return functions
- Declarative Style: Describe what you want, not how to do it
Pure Functions
A pure function depends only on its inputs and produces no side effects:
# Pure function - always returns same result for same input
def add(a, b):
return a + b
add(2, 3) # Always 5
add(2, 3) # Always 5
# Impure function - depends on external state
total = 0
def add_to_total(value):
global total
total += value # Side effect: modifies external state
return total
# Impure function - has side effects
def save_user(user):
database.save(user) # Side effect: I/O operation
return user
# Making it more functional
def validate_user(user):
# Pure - just validates, returns result
if not user.get('email'):
return {'valid': False, 'error': 'Email required'}
return {'valid': True, 'user': user}
Lambda Functions
Anonymous functions for simple operations:
# Regular function
def square(x):
return x ** 2
# Lambda equivalent
square = lambda x: x ** 2
# Common uses
numbers = [1, 2, 3, 4, 5]
# Sort by custom key
users = [{'name': 'Bob', 'age': 30}, {'name': 'Alice', 'age': 25}]
sorted_users = sorted(users, key=lambda u: u['age'])
# Quick calculations
double = lambda x: x * 2
add = lambda a, b: a + b
is_even = lambda x: x % 2 == 0
# Conditional expression
abs_value = lambda x: x if x >= 0 else -x
Map, Filter, Reduce
The three pillars of functional data processing:
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# MAP: Apply function to each element
# Imperative
squared = []
for n in numbers:
squared.append(n ** 2)
# Functional
squared = list(map(lambda x: x ** 2, numbers))
# Or with list comprehension (Pythonic)
squared = [x ** 2 for x in numbers]
# Result: [1, 4, 9, 16, 25]
# FILTER: Keep elements that match condition
# Imperative
evens = []
for n in numbers:
if n % 2 == 0:
evens.append(n)
# Functional
evens = list(filter(lambda x: x % 2 == 0, numbers))
# Or with list comprehension
evens = [x for x in numbers if x % 2 == 0]
# Result: [2, 4]
# REDUCE: Combine all elements into one value
# Imperative
total = 0
for n in numbers:
total += n
# Functional
total = reduce(lambda acc, x: acc + x, numbers)
# Or use built-in sum()
total = sum(numbers)
# Result: 15
# Chaining operations
result = reduce(
lambda acc, x: acc + x,
map(lambda x: x ** 2,
filter(lambda x: x % 2 == 0, numbers))
)
# Filter evens [2, 4] -> Square [4, 16] -> Sum = 20
List Comprehensions
Python's preferred way to do functional-style transformations:
# Basic comprehension
squares = [x**2 for x in range(10)]
# With condition (filter)
even_squares = [x**2 for x in range(10) if x % 2 == 0]
# Dictionary comprehension
word_lengths = {word: len(word) for word in ['hello', 'world']}
# Set comprehension
unique_lengths = {len(word) for word in ['hello', 'world', 'hi']}
# Nested comprehension
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Generator expression (lazy evaluation)
squares_gen = (x**2 for x in range(1000000)) # Doesn't compute yet
first_ten = [next(squares_gen) for _ in range(10)] # Computes on demand
Higher-Order Functions
Functions that take functions as arguments or return functions:
# Function that takes a function
def apply_twice(func, value):
return func(func(value))
result = apply_twice(lambda x: x * 2, 3) # 12
# Function that returns a function
def multiplier(n):
def multiply(x):
return x * n
return multiply
double = multiplier(2)
triple = multiplier(3)
double(5) # 10
triple(5) # 15
# Decorator pattern (very common in Python)
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Returned {result}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
add(2, 3) # Prints: Calling add, Returned 5
Immutability
Prefer creating new data over modifying existing data:
# Mutable approach (avoid)
def add_item_mutable(items, item):
items.append(item) # Modifies original list
return items
# Immutable approach (prefer)
def add_item_immutable(items, item):
return items + [item] # Creates new list
# With dictionaries
def update_user_mutable(user, key, value):
user[key] = value # Modifies original
return user
def update_user_immutable(user, key, value):
return {**user, key: value} # Creates new dict
# Named tuples for immutable data structures
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p1 = Point(1, 2)
# p1.x = 3 # Error! Immutable
# Create new point with different value
p2 = Point(3, p1.y)
# Dataclasses with frozen=True
from dataclasses import dataclass
@dataclass(frozen=True)
class User:
name: str
email: str
user = User('John', 'john@example.com')
# user.name = 'Jane' # Error! Frozen
Functional Tools in Python
from functools import partial, reduce, lru_cache
from itertools import chain, groupby, takewhile, dropwhile
from operator import add, mul, itemgetter
# partial: Pre-fill some arguments
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
# lru_cache: Memoization (caching)
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# itertools examples
numbers = [[1, 2], [3, 4], [5, 6]]
flat = list(chain.from_iterable(numbers)) # [1, 2, 3, 4, 5, 6]
# Group by
users = [
{'name': 'Alice', 'dept': 'Engineering'},
{'name': 'Bob', 'dept': 'Sales'},
{'name': 'Carol', 'dept': 'Engineering'},
]
sorted_users = sorted(users, key=itemgetter('dept'))
for dept, group in groupby(sorted_users, key=itemgetter('dept')):
print(f"{dept}: {list(group)}")
# operator module for common operations
numbers = [1, 2, 3, 4, 5]
total = reduce(add, numbers) # Instead of lambda a,b: a+b
product = reduce(mul, numbers)
Practical Example
# Processing a list of orders - functional style
orders = [
{'id': 1, 'total': 150, 'status': 'completed'},
{'id': 2, 'total': 50, 'status': 'pending'},
{'id': 3, 'total': 200, 'status': 'completed'},
{'id': 4, 'total': 75, 'status': 'completed'},
]
# Get total revenue from completed orders over $100
# Imperative approach
total = 0
for order in orders:
if order['status'] == 'completed' and order['total'] > 100:
total += order['total']
# Functional approach
from functools import reduce
total = reduce(
lambda acc, order: acc + order['total'],
filter(
lambda o: o['status'] == 'completed' and o['total'] > 100,
orders
),
0 # Initial value
)
# Pythonic approach (list comprehension + sum)
total = sum(
order['total']
for order in orders
if order['status'] == 'completed' and order['total'] > 100
)
# Result: 350 (150 + 200)
When to Use Functional Programming
- Data transformations: Processing lists, filtering, mapping
- Stateless operations: Pure calculations without side effects
- Concurrent programming: Immutability prevents race conditions
- Testing: Pure functions are easy to test
Balance is key: Python is multi-paradigm. Use functional style where it makes code clearer, but don't force it everywhere.
Master Python with Expert Mentorship
Our Full Stack Python program covers functional programming and other paradigms. Learn to write clean, maintainable Python code with personalized guidance.
Explore Full Stack Python Program