Decorator in Python

python

One interesting and powerful feature in Python is the decorator. Decorators allow you to modify the behavior of functions or classes in a very flexible and reusable way, without changing their actual code. This makes them extremely useful for logging, enforcing access control, instrumentation, caching, and more.

Example: Using a Decorator for Logging

Let's say we want to log when a function is called. Instead of adding logging code inside every function, we can use a decorator.

Step 1: Define the Decorator

A decorator is simply a function that takes another function and extends its behavior.

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function {func.__name__} with arguments {args} and {kwargs}")
        result = func(*args, **kwargs)
        print(f"Function {func.__name__} returned {result}")
        return result
    return wrapper

Step 2: Use the Decorator

You can apply the decorator to any function using the `@decorator_name` syntax.

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

@log_decorator
def multiply(a, b):
    return a * b

# Test the decorated functions
result_add = add(2, 3)
result_multiply = multiply(4, 5)

What Happens Here?

1. Defining the Decorator:

  • `log_decorator` is defined to take a function `func` as an argument.
  • Inside `log_decorator`, a new function `wrapper` is defined, which wraps around the original function `func`.
  • The `wrapper` function logs the function name and arguments before calling `func`, and logs the result after calling `func`.

2. Applying the Decorator:

  •   The `@log_decorator` syntax applies the decorator to the functions `add` and `multiply`.
  •   When `add(2, 3)` or `multiply(4, 5)` is called, it actually calls the `wrapper` function defined inside the decorator.

Output:

Calling function add with arguments (2, 3) and {}
Function add returned 5
Calling function multiply with arguments (4, 5) and {}
Function multiply returned 20

Why is This Interesting?

Decorators are a prime example of Python’s flexibility and the power of higher-order functions. They allow for the separation of concerns, letting you add or modify functionality in a clean, reusable way without altering the core logic of the functions they decorate. This can lead to cleaner, more maintainable, and more readable code.

Advanced Use Cases

  • Caching: Implementing memoization to cache expensive function calls.
  • Access Control: Checking user permissions before allowing access to certain functions.
  • Instrumentation: Automatically timing function execution or counting how often a function is called.

Example: Caching with a Decorator

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Test the caching decorator
print(fibonacci(100))  # Computes the 100th Fibonacci number efficiently

Here, the `lru_cache` decorator from the `functools` module caches the results of the `fibonacci` function, making subsequent calls with the same arguments return much faster.

Decorators highlight Python’s expressive syntax and its emphasis on readability and simplicity, making them a fascinating and useful feature of the language.

Tags