Try   HackMD

Python Decorators and Aspect-Oriented Programming (AOP)

Basic Usage

To define a decorator, the basic form is:

def my_decorator(func): def wrapper(*args, **kwargs): # ...do something before the function run # Run the function. The result is what the function returns result = func(*args, **kwargs) # ...do something after the function run # you can use the "result" of the function here return result # Ensure the result is returned from the wrapper return wrapper

and to use this decorator, simply use:

@my_decorator def my_func(foo, bar): return f'foo={foo}, bar={bar}' # Call the function my_func(foo='a', bar='b')

In the sample code above:

  • func: The function that the decorator is applied to.
    • In the example, it's my_func.
  • *args, **kwargs: The arguments of the function that the decorator is applied to.
    • In the example, it's foo='a', bar='b'.

Therefore, func(*args, **kwargs) executes the original function. By using a decorator, we can insert logic before and after the function runs, which is essentially the concept of Aspect-Oriented Programming (AOP).

Modifying the Return Value

A decorator can modify the return value of the function it decorates. For example:

def modify_return_decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) # Modify the result before returning modified_result = f'Modified: {result}' return modified_result return wrapper @modify_return_decorator def my_func(foo, bar): return f'foo={foo}, bar={bar}' # Call the function print(my_func(foo='a', bar='b')) # Output will be 'Modified: foo=a, bar=b'

In this example, the decorator modify_return_decorator modifies the return value of my_func by prepending "Modified: " to it.

Advanced Usage

Multiple Decorators

Decorators can also take arguments. To achieve this, you need an extra layer of function:

def my_decorator_with_args(arg1, arg2): def decorator(func): def wrapper(*args, **kwargs): # ...do something before the function runs print(f'Arguments passed to decorator: {arg1}, {arg2}') # Run the function. The result is what the function returns result = func(*args, **kwargs) # ...do something after the function runs return result return wrapper return decorator @my_decorator_with_args('foo', 'bar') def my_func(foo, bar): return f'foo={foo}, bar={bar}' # Call the function my_func(foo='a', bar='b')

Class Decorators

Decorators can also be applied to classes to modify or extend their behavior:

def class_decorator(cls): class Wrapped(cls): def new_method(self): print("New method added by the decorator.") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) print("Instance of decorated class created.") return Wrapped @class_decorator class MyClass: def __init__(self, value): self.value = value # Create an instance of the decorated class obj = MyClass(10) obj.new_method()

In this example, the class MyClass is wrapped with additional functionality provided by the decorator.

By utilizing decorators, you can effectively implement AOP concepts in Python, allowing you to modularize cross-cutting concerns such as logging, authentication, and transaction management. Decorators provide a powerful and flexible way to extend and modify the behavior of functions and classes.