In Python, functions are first-class citizens. This means functions can be assigned to variables, passed as arguments into other functions, and returned from functions just like integers or strings. A decorator leverages this capability to wrap another function, extending or modifying its behavior without altering its underlying code.
Figure 35.1: Decorator wrapping execution: pre-execution setup -> target function invocation -> post-execution cleanup.
Let us write a decorator that logs when a function starts and finishes:
import functools
def log_execution(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[LOG] Executing function: '{func.__name__}'...")
result = func(*args, **kwargs)
print(f"[LOG] Completed '{func.__name__}' successfully!")
return result
return wrapper
# Applying the decorator using @ syntax:
@log_execution
def generate_bill(customer_name: str, total_amount: float):
print(f"Generating invoice for {customer_name}: ₹{total_amount}")
return True
# Call decorated function:
generate_bill("Deepak Kumar", 4500.0)
[LOG] Executing function: 'generate_bill'...
Generating invoice for Deepak Kumar: ₹4500.0
[LOG] Completed 'generate_bill' successfully!
import time
import functools
def benchmark(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start_time
print(f"[BENCHMARK] {func.__name__}() took {elapsed:.6f} seconds to run.")
return result
return wrapper
@benchmark
def calculate_large_sum():
return sum(x ** 2 for x in range(1_000_000))
calculate_large_sum()