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.

Try Code in Editor

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. 1

    Define a outer decorator function that takes the target function as an argument.

  2. 2

    Import and apply @functools.wraps(func) on your nested wrapper function to protect original metadata.

  3. 3

    Implement wrapper(*args, **kwargs) to capture input variables, inject logic, and invoke target function.

  4. 4

    Return the wrapper function object from the decorator header block.

  5. 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.

decorators_tutorial.py
Try in Editor
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__)
Terminal Output
[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.