Leverage functions as first-class objects to write robust, parameterizable and transparent decorators.
Open this lesson in KodokonIn Python, a function is an object like any other: you can assign it to a variable, pass it as an argument, return it. A higher-order function takes or returns a function - sorted(data, key=...) is one. Add closures, where an inner function captures the variables of the enclosing function and keeps them alive after it returns, and you have all the ingredients of decorators.
def make_multiplier(factor):
def multiply(value):
return value * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10)) # 20 30A decorator is a function that receives a function and returns another one - usually a wrapper that adds behavior around the call. The syntax @timed above def slow_sum is strict sugar for slow_sum = timed(slow_sum). The wrapper accepts *args, kwargs to stay agnostic about the signature, and returns** the result of the decorated function - forgetting this is a classic mistake that turns every return value into None.
import functools
import time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
delay = time.perf_counter() - start
print(f"{func.__name__}: {delay:.4f}s")
return result
return wrapper
@timed
def slow_sum(n):
return sum(range(n))
print(slow_sum(1_000_000))To parameterize a decorator (@repeat(times=3)), you need one more level: a factory that receives the parameters and returns the decorator, which in turn returns the wrapper. Three nested functions - that is the price of flexibility. If the state gets complex, a class with a __call__ method is often more readable. Reserve decorators for cross-cutting concerns: logging, caching, retry, access control. A decorator that alters business logic hides the essential from the reader.
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Hello {name}")
greet("Ada")