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.
my_func
.*args
, **kwargs
: The arguments of the function that the decorator is applied to.
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).
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.
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')
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.