How to Create and Use Decorators in Python
Learn how to write and apply decorators in Python. Master wrapping functions, modifying behavior, handling arguments, and preserving metadata with wraps.
Explanation
Decorators are one of Python's most elegant and powerful syntax features, allowing you to modify the behavior of a function or class without permanently changing its code. They are widely used for cross-cutting concerns like logging execution times, enforcing user authentication, caching results (memoization), and auditing API endpoints.
Under the hood, a decorator is a higher-order function that takes another function as an argument, defines a nested wrapper function that injects some custom logic, and returns this wrapper function. Python provides a clean shorthand syntax using the `@` symbol to apply decorators to functions, making them visually elegant.
When writing decorators, you will typically want the decorated function to support any number of positional and keyword arguments. You can achieve this by designing the nested wrapper function to accept `*args` and `**kwargs` and pass them to the target function. Additionally, you should use `functools.wraps` on the wrapper function to preserve the original function's docstring and name metadata.
Step-by-Step Implementation
- 1
Define a outer decorator function that takes the target function as an argument.
- 2
Import and apply @functools.wraps(func) on your nested wrapper function to protect original metadata.
- 3
Implement wrapper(*args, **kwargs) to capture input variables, inject logic, and invoke target function.
- 4
Return the wrapper function object from the decorator header block.
- 5
Apply the decorator using @decorator_name directly above the target function signature.
Code Example
This script demonstrates writing a logging decorator and wrapping a function to inspect inputs, outputs, and metadata.
from functools import wraps
# 1. Defining a decorator
def log_execution(func):
@wraps(func) # Preserves original function metadata
def wrapper(*args, **kwargs):
print(f"[Log] Calling function: {func.__name__} with args: {args}")
result = func(*args, **kwargs)
print(f"[Log] Finished {func.__name__}. Result: {result}")
return result
return wrapper
# 2. Applying the decorator using @ syntax
@log_execution
def add_numbers(a, b):
"""Adds two numbers and returns the sum."""
return a + b
# 3. Invoking the decorated function
sum_result = add_numbers(15, 25)
print("Sum Result:", sum_result)
# Verifying metadata preservation
print("Docstring preserved:", add_numbers.__doc__)[Log] Calling function: add_numbers with args: (15, 25)
[Log] Finished add_numbers. Result: 40
Sum Result: 40
Docstring preserved: Adds two numbers and returns the sum.Frequently Asked Questions
Why is @functools.wraps necessary inside decorators?
Without wraps, the decorated function loses its original identity (its __name__ and __doc__ attributes are replaced by those of the nested wrapper function), which makes debugging and code introspection difficult.
Can I apply multiple decorators to a single function?
Yes! You can chain decorators by stack-placing multiple @ lines. Python applies them from the bottom up (closest to the function definition executes first).
Related How-To Guides
Recommended Python Resources
Expand your knowledge with related interactive tutorials, cheat sheets, and code comparisons.
Python Variables & Data Types Explained
Understand Python variables and core data types (strings, integers, floats, booleans). A complete beginner guide to memory assignment in Python.
Python Dictionary Methods
Learn Python dictionary methods. Complete reference guide for key-value pair insertions, retrievals, updates, and checks.
Python vs JavaScript: Which Programming Language is Best?
A comprehensive comparison between Python and JavaScript. Explore syntax differences, performance, use cases (backend vs frontend), and coding examples.