Function Factories¶
A function factory is a function that creates and returns another function. This pattern leverages closures to generate specialized functions dynamically.
Basic Pattern¶
A function factory returns an inner function that captures variables from the enclosing scope:
def make_multiplier(n: int): # returns a function: int -> int
def multiply(x: int) -> int:
return x * n
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
Each call to make_multiplier creates a new function with its own captured value of n.
How Closures Work¶
The inner function multiply remembers the value of n even after make_multiplier has finished executing. This combination of a function and the variables it captures from its enclosing scope is called a closure.
def make_adder(n: int): # returns a function: int -> int
def add(x: int) -> int:
return x + n
return add
add_10 = make_adder(10)
add_100 = make_adder(100)
print(add_10(5)) # 15
print(add_100(5)) # 105
add_10 and add_100 are independent closures — each holds its own copy of n.
Practical Examples¶
Validators¶
def make_range_validator(min_val: float, max_val: float): # returns a function: float -> bool
def validate(x: float) -> bool:
return min_val <= x <= max_val
return validate
valid_percentage = make_range_validator(0, 100)
valid_age = make_range_validator(0, 150)
print(valid_percentage(50)) # True
print(valid_percentage(150)) # False
print(valid_age(25)) # True
Greeting Functions¶
def make_greeter(greeting: str): # returns a function: str -> str
def greet(name: str) -> str:
return f"{greeting}, {name}!"
return greet
say_hello = make_greeter("Hello")
say_hi = make_greeter("Hi")
print(say_hello("Alice")) # Hello, Alice!
print(say_hi("Bob")) # Hi, Bob!
Formatters¶
def make_formatter(prefix: str, suffix: str = ""): # returns a function: value -> str
def format_value(value) -> str:
return f"{prefix}{value}{suffix}"
return format_value
format_currency = make_formatter("\$", " USD")
format_percent = make_formatter("", "%")
print(format_currency(100)) # $100 USD
print(format_percent(85.5)) # 85.5%
Power Functions¶
def make_power(exp: int): # returns a function: float -> float
def power(base: float) -> float:
return base ** exp
return power
square = make_power(2)
cube = make_power(3)
print(square(4)) # 16
print(cube(4)) # 64
Modifying Captured Variables with nonlocal¶
A closure can read captured variables freely, but to modify them it needs the nonlocal keyword. Without nonlocal, an assignment inside the inner function would create a new local variable instead of updating the captured one.
def make_counter(start: int = 0, step: int = 1): # returns a function: () -> int
count = start
def counter() -> int:
nonlocal count
current = count
count += step
return current
return counter
counter = make_counter()
print(counter()) # 0
print(counter()) # 1
print(counter()) # 2
by_tens = make_counter(start=10, step=10)
print(by_tens()) # 10
print(by_tens()) # 20
Late Binding in Loops¶
Creating closures inside a loop is a common source of bugs. Because closures capture variables by reference (not by value), all the closures share the same variable — and see its final value after the loop ends.
# Bug: all functions capture the same variable i
funcs = []
for i in range(3):
funcs.append(lambda x: x + i)
print(funcs[0](10)) # 12 (not 10!)
print(funcs[1](10)) # 12 (not 11!)
# Fix: capture the current value with a default parameter
funcs = []
for i in range(3):
funcs.append(lambda x, i=i: x + i)
print(funcs[0](10)) # 10
print(funcs[1](10)) # 11
Key Ideas¶
Function factories produce specialized functions from a common template. The inner function captures variables from the enclosing scope, forming a closure that remembers those values across calls. This is useful whenever you need a family of related functions that differ only in their configuration — validators, formatters, converters, and similar patterns.
When the inner function needs to modify a captured variable (not just read it), use nonlocal. When your factory grows complex enough to need multiple methods or shared mutable state, consider using a class instead.
For more on the decorator pattern — a close relative of function factories — see the decorators chapter.