-
Notifications
You must be signed in to change notification settings - Fork 196
1.4 Decorators
Decorators take a function as argument and returns a function. They are used to extend the behavior of the wrapped function, without modifying it. So they are very useful for dealing with code legacy.
You can define decorators in any programming language by using the following design pattern:
def my_decorator(fn):
def _my_decorator():
print("Before the function")
fn()
print("After the function")
return _my_decorator
def my_decorated_function():
print("Function")
# with this usage you should create a new object once by using decorator manually
my_decorated_function = my_decorator(my_decorated_function)
Note that we are using a single leading underscore to follow the "inner use" convention.
In Python you can relate your functions with decorators by using a line starting with @
before the function's signature.
def my_decorator(fn):
def _my_decorator():
print("Before the function")
fn()
print("After the function")
return _my_decorator
@my_decorator
def my_decorated_function():
print("Function")
Most of the time you should also pass the arguments to the wrapped functions. You can use *args
and **kwargs
to do this easily. You can also modify the input arguments or return values as you wish.
def decorator(func):
def _decorator(*args, **kwargs):
print("Before the function")
print(args)
print(kwargs)
func(*args, **kwargs)
print("After the function")
return _decorator
@decorator
def decorated_func_w_args(x):
print(f"x = {x}")
You may also want to use separate arguments for a decorator. You can create a decorator which can accept its own parameters by adding one more level as follows:
def decorator(arg1, arg2):
def real_decorator(func):
def _decorator(*args, **kwargs):
print(f"The arguments are: {arg1}, {arg2}")
print("Before the function")
print(args)
print(kwargs)
func(*args, **kwargs)
print("After the function")
return _decorator
return real_decorator
@decorator("Bora", "Canbula")
def my_function(s):
print(s)
my_function("Parallel Programming")
If you need to use a more complex setup, you can also define your decorator as a class by using its __call__
method:
class DecoratorClass:
def __init__(self, arg1, arg2):
self.arg1 = arg1
self.arg2 = arg2
def __call__(self, func):
def decorator(*args, **kwargs):
print(f"The arguments are: {self.arg1}, {self.arg2}")
print("Before the function")
print(args)
print(kwargs)
func(*args, **kwargs)
print("After the function")
return decorator
@DecoratorClass("Bora", "Canbula")
def my_function(s):
print(s)
my_function("Parallel Programming")
In Python, it is allowed to use more than one decorator, so you can create a decorator chain:
@decorator("Celal", "Bayar")
@DecoratorClass("Bora", "Canbula")
def my_function(s):
print(s)
my_function("Parallel Programming")
This is the end of the section 0, which is pre-required knowledge for Parallel Programming course. You can test yourself by writing a decorator, which checks the type hints of arguments and raises TypeError if the values are not relevant with the types.