# Python Decorators and Aspect-Oriented Programming (AOP) ## Basic Usage To define a decorator, the basic form is: ```python= 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: ```python= @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: ```python= 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: ```python= 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: ```python= 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.