Decorators in Python: Fundamentals for Data Scientists
By: Erdem Isbilen
Tags: Python, Data Science, Decorators, Programming, Object Oriented

Decorators in Python: Fundamentals for Data Scientists

Understand the basics with a concrete example!

Photo by Goran Ivos on Unsplash

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:

Decorator Definition

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

def 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.

which also contains a wrapper function inside. Decorator functions should take a target function as a parameter.

as a parameter. And it should return wrapper function which extends the target function passed as an argument.

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.

Photo by Doruk Yemenici on Unsplash

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_logger

def 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.

Photo by Doruk Yemenici on Unsplash

Defining Multiple Decorators and Decorating Functions with Arguments

import time def 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_func def 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_timeit

def target(loop):

count = 0

print('Python is in the decorated target function')

for number in range(loop):

count += number target(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.

Key Takeaways

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.

Conclusion

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!