Decorators in Python: Fundamentals for Data Scientists | by Erdem Isbilen | Towards Data Science

Decorators in Python is used to extend the functionality of a callable object without modifying its structure. Basically, decorator functions wrap another function to enhance or modify its behaviour.
This post will introduce you to the basics of decorators in Python.
Let’s write a Python3 code that contains examples of decorator implementations:
def decorator_func_logger(target_func): def wrapper_func():   print("Before calling", target_func.__name__)   target_func()   print("After calling", target_func.__name__)  return wrapper_funcdef target(): print('Python is in the decorated target function')dec_func = decorator_func_logger(target)dec_func()Output:air-MacBook-Air:$ python DecoratorsExample.py('Before calling', 'target')Python is in the decorated target function('After calling', 'target')
Above decorator structure helps us to display some notes on the console before and after a target function is called.
Here are the simple steps for defining a decorator;
First, we should define a callable object such as a decorator function which also contains a wrapper function inside.
Decorator functions should take a target function as a parameter.
And it should return wrapper function which extends the target function passed as an argument.
Wrapper function should contain a target function call together with the code extending the behaviour of target function.
def decorator_func_logger(target_func): def wrapper_func():   print("Before calling", target_func.__name__)   target_func()   print("After calling", target_func.__name__)  return wrapper_func@decorator_func_loggerdef target(): print('Python is in the decorated target function')target()Output:air-MacBook-Air:$ python DecoratorsExample.py('Before calling', 'target')Python is in the decorated target function('After calling', 'target')
With the help of the syntactic sugar provided by Python, we can simplify the decorator definition as shown above.
Note that @decorator_func_logger is added just before the target function we would like to decorate. We can then directly call the target function. There is no need to explicitly assign the decorator, as we did in the first example.
import timedef decorator_func_logger(target_func): def wrapper_func(*args, **kwargs):  print("Before calling", target_func.__name__)  target_func(*args, **kwargs)  print("After calling", target_func.__name__) return wrapper_funcdef decorator_func_timeit(target_func): def wrapper_func(*args, **kwargs):  ts = time.time()  target_func(*args, **kwargs)  te = time.time()  print (target_func.__name__, (te - ts) * 1000) return wrapper_func@decorator_func_logger@decorator_func_timeitdef target(loop): count = 0 print('Python is in the decorated target function') for number in range(loop):  count += numbertarget(100)target(3000)Output:air-MacBook-Air:$ python DecoratorsExample.py('Before calling', 'wrapper_func')Python is in the decorated target function('target', 0.015974044799804688)('After calling', 'wrapper_func')('Before calling', 'wrapper_func')Python is in the decorated target function('target', 0.47397613525390625)('After calling', 'wrapper_func')
You can easily decorate a target function with multiple decorators, by adding several decorators before the target function with using ‘@’ syntax. The order that the decorators are being executed will be the same as how they are listed before target function.
Note that we have a parameter, loop, in our target function. This is no issue as long as the same parameter is used for the wrapper function. To make sure that the decorator is flexible to take an arbitrary number of parameters, (*args, **kwargs) parameters are used for the wrapper function.
Decorators define reusable code blocks that you can apply to a callable object (functions, methods, classes, objects) to modify or extend its behaviour without modifying the object itself.
Consider that you have many functions in your script performing many different tasks and you need to add specific behaviour to all of your functions. In such a case, it is not a good solution to copy the same code block into your functions to have the required functionality. You can simply decorate your functions instead.
In this post, I explained the basics of decorators in Python.
The code in this post is available in my GitHub repository.
I hope you found this post useful.
Thank you for reading!