Master Python Decorators: Boost Your Functions in Minutes
This article explains Python decorators with clear analogies, walks through the underlying concepts of higher‑order functions and closures, provides practical code examples such as timing and logging decorators, discusses common pitfalls like lost metadata, and shows how to preserve it using functools.wraps.
1. What is a decorator? A simple metaphor
Imagine a core function like a delivery service. A decorator works like a packaging worker who adds steps before and after the delivery—such as a phone call before sending and a signature after—without changing the core logic.
It takes your core function.
Adds new behavior (e.g., logging, timing) around it.
Returns a new, enhanced function.
The essence is adding functionality dynamically without modifying the original function’s code.
2. Core foundations: Understanding closures and higher‑order functions
2.1 Higher‑order function
A function that can accept another function as an argument and/or return a function. This is the skeleton of a decorator.
def outer_func(func):
def inner_func():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return inner_func
def say_hello():
print("Hello!")
enhanced_say_hello = outer_func(say_hello)
enhanced_say_hello()Output:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.2.2 Closure
The inner function remembers the variables of the outer function even after the outer function has finished, which is the soul of a decorator.
3. Magic moment: The @ syntax sugar
Python’s @ symbol provides a concise way to apply a decorator.
def outer_func(func):
def inner_func():
print("Before calling...")
func()
print("After calling...")
return inner_func
@outer_func
def say_hello():
print("Hello!")
say_hello()Using @outer_func is equivalent to say_hello = outer_func(say_hello). The execution order is:
Define the original function.
Pass it to the decorator.
Replace the original name with the returned wrapper.
4. Hands‑on: Useful decorator examples
4.1 Timer decorator
import time
def timer(func):
"""Calculate function execution time"""
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
return result
return wrapper
@timer
def long_running_function(n):
"""Simulate a time‑consuming function"""
for i in range(n):
sum([j**2 for j in range(10000)])
return "Done"
result = long_running_function(10)
print(result)Sample output:
Finished 'long_running_function' in 0.1234 secs
Done4.2 Logging decorator
def logger(func):
"""Log function call information"""
def wrapper(*args, **kwargs):
print(f"[INFO] Calling function: {func.__name__}")
print(f"[INFO] Arguments: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"[INFO] Function {func.__name__} finished.")
return result
return wrapper
@logger
def add_numbers(a, b):
return a + b
print(add_numbers(3, b=4))Sample output:
[INFO] Calling function: add_numbers
[INFO] Arguments: (3,), {'b': 4}
[INFO] Function add_numbers finished.
75. Solving a common side‑effect
After decoration, the original function’s metadata (e.g., __name__, __doc__) is replaced by the wrapper’s metadata.
print(long_running_function.__name__) # Outputs: 'wrapper'Solution: use functools.wraps inside the decorator to preserve the original metadata.
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
print(f"Finished {func.__name__!r} in {end_time - start_time:.4f} secs")
return result
return wrapper6. Summary and key takeaways
Key Point
Description
Essence
A higher‑order function that returns a function, leveraging closures.
Purpose
Dynamically enhance function behavior without altering its internal code, following the open‑closed principle.
Core
The @ syntax is just syntactic sugar for func = decorator(func).
Best practice
Always apply functools.wraps to keep the original function’s metadata.
Advantages
Decoupling, code reuse, and flexible composition (multiple decorators can be stacked).
Decorators appear everywhere in Python—from web frameworks ( @app.route) to testing ( @patch) and plugin systems. Mastering them elevates your Python expertise to a new level.
Python Crawling & Data Mining
Life's short, I code in Python. This channel shares Python web crawling, data mining, analysis, processing, visualization, automated testing, DevOps, big data, AI, cloud computing, machine learning tools, resources, news, technical articles, tutorial videos and learning materials. Join us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
