Python Decorator Pattern

The core idea of the decorator pattern is wrapping — by creating a wrapper object (decorator) to wrap the original object, thereby extending its functionality without modifying the original object.

Basic Concepts

The decorator pattern consists of four main roles:

  1. Component Interface (Component): Defines the common interface for the decorated object and the decorator
  2. Concrete Component: The original object that needs to be decorated
  3. Decorator Base Class (Decorator): Holds a reference to a component object and implements the component interface
  4. Concrete Decorator: Implements the specific decoration functionality


Basic Syntax of Decorators

Function Decorators

Function decorators are the most common form of decorators. They take a function as an argument and return a new function.

Example

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Some operations before function execution")
        result = func(*args, **kwargs)
        print("Some operations after function execution")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"Hello, {name}!")

# Use decorator
say_hello("Alice")

Output:

函数执行前的一些操作
Hello, Alice!
函数执行后的一些操作

Decorators with Parameters

If you need to pass parameters to the decorator, you need to nest another layer of functions:

Example

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(times):
                print(f"Execution {i+1}:")
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def greet(name):
    print(f"Hello, {name}!")

greet("Bob")

Class Decorators

Besides function decorators, Python also supports class decorators. Class decorators work by implementing__call__methods to work.

Basic Class Decorator

Example

class TimerDecorator:
    def __init__(self, func):
        self.func = func
   
    def __call__(self, *args, **kwargs):
        import time
        start_time = time.time()
        result = self.func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {self.func.__name__} executed in {end_time - start_time:.4f} seconds")
        return result

@TimerDecorator
def calculate_sum(n):
    return sum(range(n))

result = calculate_sum(1000000)
print(f"Calculation result: {result}")

Class Decorator with Parameters

Example

class LogDecorator:
    def __init__(self, level="INFO"):
        self.level = level
   
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(f"[{self.level}] Calling function: {func.__name__}")
            print(f"[{self.level}] Arguments: args={args}, kwargs={kwargs}")
            result = func(*args, **kwargs)
            print(f"[{self.level}] Return value: {result}")
            return result
        return wrapper

@LogDecorator(level="DEBUG")
def multiply(a, b):
    return a * b

multiply(5, 3)

Built-in Decorators

Python provides some useful built-in decorators:

@staticmethod and @classmethod

Example

class Calculator:
    @staticmethod
    def add(x, y):
        return x + y
   
    @classmethod
    def multiply(cls, x, y):
        return x * y

# Use static method
result1 = Calculator.add(5, 3)
print(f"Static method result: {result1}")

# Use class method
result2 = Calculator.multiply(5, 3)
print(f"Class method result: {result2}")

@property

Example

class Circle:
    def __init__(self, radius):
        self._radius = radius
   
    @property
    def radius(self):
        return self._radius
   
    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("Radius must be a positive number")
        self._radius = value
   
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

circle = Circle(5)
print(f"Radius: {circle.radius}")
print(f"Area: {circle.area}")

circle.radius = 10
print(f"New radius: {circle.radius}")
print(f"New area: {circle.area}")

Practical Application Scenarios of Decorators

1. Logging

Example

def log_execution(func):
    def wrapper(*args, **kwargs):
        print(f"Start executing: {func.__name__}")
        try:
            result = func(*args, **kwargs)
            print(f"Successfully completed: {func.__name__}")
            return result
        except Exception as e:
            print(f"Execution failed: {func.__name__}, error: {e}")
            raise
    return wrapper

@log_execution
def process_data(data):
    # Simulate data processing
    if not data:
        raise ValueError("Data cannot be empty")
    return [x * 2 for x in data]

# Test
data = [1, 2, 3]
result = process_data(data)
print(f"Processing result: {result}")

2. Permission Verification

Example

def require_login(func):
    def wrapper(user, *args, **kwargs):
        if not user.get('is_authenticated', False):
            raise PermissionError("User not logged in")
        return func(user, *args, **kwargs)
    return wrapper

@require_login
def view_profile(user):
    return f"View profile of user {user['username']}"

# Test
user1 = {'username': 'alice', 'is_authenticated': True}
user2 = {'username': 'bob', 'is_authenticated': False}

print(view_profile(user1))  # Normal execution
# print(view_profile(user2)) # will raise PermissionError

3. Cache Decorator

Example

def cache_results(func):
    cache = {}
   
    def wrapper(*args):
        if args in cache:
            print(f"Get result from cache: {args}")
            return cache[args]
        result = func(*args)
        cache[args] = result
        print(f"Calculate and cache result: {args} -> {result}")
        return result
    return wrapper

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

# Test cache effect
print(fibonacci(5))
print(fibonacci(5))  # This time it will get from cache

Execution Order of Multiple Decorators

When using multiple decorators, their execution order is from bottom to top:

Example

def decorator1(func):
    def wrapper():
        print("Decorator 1 - before")
        func()
        print("Decorator 1 - after")
    return wrapper

def decorator2(func):
    def wrapper():
        print("Decorator 2 - before")
        func()
        print("Decorator 2 - after")
    return wrapper

@decorator1
@decorator2
def my_function():
    print("Original function")

my_function()

Output:

装饰器1 - 前
装饰器2 - 前
原始函数
装饰器2 - 后
装饰器1 - 后

Preserving Function Metadata

When using decorators, the original function's metadata (such as function name, docstring, etc.) is overwritten by the wrapper function. You can usefunctools.wrapsto preserve this information:

Example

from functools import wraps

def preserve_metadata(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Docstring of the wrapper function"""
        return func(*args, **kwargs)
    return wrapper

@preserve_metadata
def example_function():
    """This is the docstring of the original function"""
    pass

print(f"Function name: {example_function.__name__}")
print(f"Docstring: {example_function.__doc__}")

Practical Exercises

Exercise 1: Creating a Performance Monitoring Decorator

Write a decorator to monitor the execution time and memory usage of a function.

Example

import time
import tracemalloc

def performance_monitor(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # Start memory tracking
        tracemalloc.start()
       
        # Record start time
        start_time = time.time()
       
        # Execute function
        result = func(*args, **kwargs)
       
        # Record end time
        end_time = time.time()
       
        # Get memory usage
        current, peak = tracemalloc.get_traced_memory()
        tracemalloc.stop()
       
        print(f"Performance report for function {func.__name__}:")
        print(f"Execution time: {end_time - start_time:.4f} seconds")
        print(f"Memory usage: current {current/1024:.2f} KB, peak {peak/1024:.2f} KB")
       
        return result
    return wrapper

@performance_monitor
def process_large_data():
    """Simulate processing large amounts of data"""
    data = [i**2 for i in range(100000)]
    return sum(data)

process_large_data()

Exercise 2: Implementing a Retry Mechanism Decorator

Create a decorator that automatically retries a specified number of times when a function execution fails.

Example

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    if attempts == max_attempts:
                        print(f"Function {func.__name__} failed, maximum retry count reached")
                        raise
                    print(f"Function {func.__name__} failed on attempt {attempts}: {e}")
                    print(f"Waiting {delay} seconds before retrying...")
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_operation():
    """Simulate an unstable operation"""
    import random
    if random.random() < 0.7:  # 70% probability of failure
        raise ValueError("Random failure")
    return "Operation succeeded"

# Test retry mechanism
result = unstable_operation()
print(f"Final result: {result}")

Summary

Python decorators are a powerful and flexible tool that provide the following advantages:

  1. Code reuse: Separates cross-cutting concerns (such as logging, caching, validation) from business logic
  2. Dynamic extension: Add new functionality without modifying the original code
  3. Keep it concise: Use@syntax makes code clearer and more readable
  4. Follows the open-closed principle: Open for extension, closed for modification

Best practices for using decorators:

  • Usagefunctools.wrapsPreserve function metadata
  • Keep the decorator's single responsibility
  • Handle exceptions in decorators properly
  • Pay attention to the execution order of decorators
Other extensions