How to Implement the Decorator Pattern in Python to Add Behavior

This Python code demonstrates the decorator pattern by wrapping a function to add logging behavior without modifying the original function.

Easy Python 3.9+ Aug 9, 2026 OOP & classes 11 views 0 copies

Python code

17 lines
Python 3.9+
import functools

def logger(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with {args} {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

@logger
def add(a, b):
    return a + b

if __name__ == "__main__":
    print(add(3, 5))

Output

stdout
Calling add with (3, 5) {}
add returned 8
8

How it works

The decorator pattern allows you to extend the behavior of a function or class without permanently modifying it. Here, the logger decorator wraps the add function, so every call prints details about the arguments and the return value. The functools.wraps decorator is used to preserve the original function's metadata (like its name and docstring) on the wrapper. This pattern is a core part of Python, and many built-in decorators like @staticmethod and @property use the same underlying mechanism. Because the wrapper accepts *args and **kwargs, it can work with any function signature, making the decorator reusable across many functions.

Common mistakes

  • Forgetting `@functools.wraps`, which loses the original function's metadata and breaks tooling like help().
  • Not returning the wrapper function from the decorator, causing the decorated name to become `None`.
  • Hardcoding argument names instead of using `*args` and `**kwargs`, which limits the decorator to only one signature.

Variations

  1. Use a class-based decorator that implements `__call__` to store state (e.g., call counts).
  2. Apply multiple decorators stacked on top of each other to add several behaviors at once.

Real-world use cases

  • Adding timing or logging to every database query in a service without touching the query functions.
  • Retrying network calls with exponential backoff by wrapping the request function in a resilient decorator.
  • Enforcing authentication or permission checks on specific endpoint handlers in a web framework.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from OOP & classes

Related tutorials and quizzes for this topic.