What are Python Decorators?
A decorator in Python is a higher-order function that allows you to modify or extend the behavior of another function without changing its actual code. Decorators are widely used in logging, authentication, caching, and other programming tasks.
Why Use Python Decorators?
✅ Enhances Functionality – Modify functions dynamically
✅ Code Reusability – Write reusable wrappers for multiple functions
✅ Keeps Code Clean – Avoids repetitive logic in multiple places
Basic Syntax of a Decorator
def decorator_function(original_function):
def wrapper_function():
print("Wrapper executed before", original_function.__name__)
return original_function()
return wrapper_function
Step-by-Step Guide with Examples
1. Creating a Simple Decorator
✅ Example: A Basic Decorator That Logs Function Execution
def log_decorator(func):
def wrapper():
print(f"Calling function: {func.__name__}")
func()
print(f"Function {func.__name__} executed successfully")
return wrapper
@log_decorator
def say_hello():
print("Hello, World!")
say_hello()
Output:
Calling function: say_hello
Hello, World! Function say_hello executed successfully
💡 Best Practice: Always use meaningful decorator names that describe their purpose.
2. Using Decorators with Arguments
✅ Example: Decorator for Timing Function Execution
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} executed in {end_time - start_time:.4f} seconds")
return result
return wrapper
@timing_decorator
def slow_function():
time.sleep(2)
print("Function completed")
slow_function()
Output:
Function completed
slow_function executed in 2.0001 seconds
💡 Best Practice: Use *args
and **kwargs
in the wrapper function to handle functions with arguments.
3. Applying Multiple Decorators
✅ Example: Combining Logging and Execution Timing Decorators
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Executing {func.__name__}()")
return func(*args, **kwargs)
return wrapper
def timing_decorator(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__}() took {end - start:.4f} seconds")
return result
return wrapper
@log_decorator
@timing_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Output:
Executing greet()
Hello, Alice! greet() took 0.0001 seconds
💡 Best Practice: The order of decorators matters. The decorator closest to the function executes first.
4. Creating a Parameterized Decorator
✅ Example: A Decorator That Accepts Arguments
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet():
print("Hello!")
greet()
Output:
Hello!
Hello!
Hello!
💡 Best Practice: Use an inner function inside the decorator to handle arguments dynamically.
5. Using functools.wraps
to Preserve Function Metadata
By default, decorators modify the function name and docstring. The functools.wraps
module preserves the original function’s metadata.
✅ Example:
import functools
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Executing {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_decorator
def add(a, b):
"""Returns the sum of two numbers."""
return a + b
print(add.__name__) # Output: add
print(add.__doc__) # Output: Returns the sum of two numbers.
💡 Best Practice: Always use @functools.wraps(func)
in decorators to avoid losing function metadata.
Real-World Applications of Decorators
✔ Logging Function Calls – Automatically log when a function is executed
✔ Caching Results – Store previous results to improve performance
✔ Authentication – Restrict access to functions based on user roles
✔ Performance Monitoring – Measure execution time of functions
When to Use Decorators?
✅ When modifying function behavior without changing the function’s code
✅ When applying reusable wrappers like logging, authentication, or timing
✅ When you need cleaner and more maintainable code
🚫 Avoid using decorators for simple cases where a direct function call works just fine.