Decorators 101

Decorators are implemented using the @decorator syntax and are essentially functions that wrap another function to extend its behavior. A basic decorator might look like this:

def simple_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before function call")
        result = func(*args, **kwargs)
        print("After function call")
        return result
    return wrapper

@simple_decorator
def greet(name):
    print(f"Hello, {name}!")

When greet("Alice") is called, the simple_decorator executes the wrapper function, which adds behavior before and after the original function.


Class-Based Decorators

While function-based decorators are common, class-based decorators can be more flexible, especially when you need to maintain state between calls.

class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Function {self.func.__name__} has been called {self.count} times.")
        return self.func(*args, **kwargs)

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

add(2, 3)
add(4, 5)

Each call to add increments the counter and prints the result. This approach is useful for metrics, logging, or rate limiting.


Stacking and Chaining Decorators

Multiple decorators can be applied to a single function in a specific order. The order matters as the execution is from the innermost to the outermost.

def decorator_one(func):
    def wrapper(*args, **kwargs):
        print("Decorator One")
        return func(*args, **kwargs)
    return wrapper

def decorator_two(func):
    def wrapper(*args, **kwargs):
        print("Decorator Two")
        return func(*args, **kwargs)
    return wrapper

@decorator_one
@decorator_two
def say_hello():
    print("Hello!")

say_hello()

In this example, the output will be:

Decorator One
Decorator Two
Hello!

Decorators with Arguments

Sometimes, you may need to pass arguments to a decorator itself. This involves an additional level of nesting.

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def print_message(msg):
    print(msg)

print_message("Hello, decorator!")

This decorator repeats the function call three times. The structure of repeat is a factory function that returns the actual decorator.


Decorators for Caching (Memoization)

Memoization is a technique to cache the results of expensive function calls. Python provides functools.lru_cache for this purpose, but implementing a custom cache can be useful for learning or specific scenarios.

from functools import wraps

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@memoize
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))  # Efficient computation due to memoization

This memoize decorator stores computed results to avoid redundant function calls.


Decorators for Access Control

Decorators can enforce access control based on user permissions or roles.

def check_permission(roles):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.role in roles:
                return func(user, *args, **kwargs)
            raise PermissionError("User does not have required permissions.")
        return wrapper
    return decorator

class User:
    def __init__(self, name, role):
        self.name = name
        self.role = role

@check_permission(['admin'])
def delete_record(user):
    print(f"Record deleted by {user.name} ({user.role})")

admin_user = User("Alice", "admin")
delete_record(admin_user)  # Succeeds

non_admin_user = User("Bob", "user")
delete_record(non_admin_user)  # Raises PermissionError

This pattern is useful for securing APIs or enforcing business rules in applications.


Comparing Decorator Approaches

TypeUse CaseMaintainabilityFlexibilityPerformance
Function DecoratorSimple behavior extensionHighMediumGood
Class DecoratorStateful behaviorMediumHighMedium
Stacked DecoratorsLayered behaviorHighHighGood
Arg-accepting DecoratorsConfigurable behaviorMediumHighMedium
Caching DecoratorsOptimizationHighMediumExcellent

Learn more with useful resources