By Author

Let’s Decorate the code using DECORATORS in Python!!!

Chesta Dhingra
Data And Beyond
Published in
6 min readJan 2, 2024

--

Welcome to beginner friendly guide that aims to enhance your coding capabilities with Python’s most powerful features name:- Decorators.

But why these decorators are crucial in python programming??

Well these decorators has a significant impact on your code by boosting functionality, promoting reusability and enhancing readability & maintainability. These are actually the pillars of effective programming regardless of whether you’re a developer, data scientist or machine learning engineer.

Understanding and utilizing the decorators can mark a difference between good code and great code.

Let’s get an overview of its importance and impact they generate:

  1. Extend and modify behavior of callable objects like functions or methods without permanently modifying them.
  2. Once the decorator is defined, one can apply to any number of functions or methods. This promotes reusability.
  3. Helps in keeping individual components of your code clean and focused on their primary purpose.
  4. Helps in Resource Management, specifically during data scientist task where we need to manage the database connections or file handles. It provide clean and consistent way to handle these resources.

To summarize it Decorators are powerful feature in python, offering dynamic and flexible way to modify the behavior of functions and methods.

Before diving into the world of Decorators it’s essential to lay a solid foundation by understanding three key concepts.

  1. Nested Functions :- which means function inside a function.
# example of nested function
def outer(x):
def inner(y):
return x+y
return inner


# to run the above example
add_five = outer(5) # here we'll be passing the value to x
# add_five will act like a func that is inner func in which value suppose
# to be pass once again
result = add_five(6)
#now when we print the output it will be 11
print(result)

2. Pass Functions as arguments:- this means functions can be passed around as variables,returned from other functions. This is crucial concept in understanding decorators, callbacks and higher-order functions. function that is passed as an argument can be called within the body of other function. Useful when we have to modufy or extend the behavior of a function dynamically.

# example of passing function as variable
def add(x,y):
return x+y

def calculate(func,x,y):#here we are passing the func in the arguments
return func(x,y) #returning the func values taht has been stored in another func

result = calculate(add, 5,6)
print(result)

3. Return function as values:- this means we are returning the function as a value, function returns another function.

def create_multiplier(x):
def multiplier(y):
return x*y
return multiplier # here we are returning the func as a value
# returning a nested func itself.

mul= create_multiplier(5)
result = mul(6)
print(result)

# another example would be

def greeting(name):
def hello():
return "Hello, "+name+"!"
return hello

greet = greeting("John")
greet()

Now that we have a solid grasp of the essential concepts needed to understand decorators, let’s embark on the next step: defining a simple decorator. From there, we’ll explore how these powerful tools can be seamlessly integrated into our daily Data Science workflows, enhancing our code and making our tasks more efficient.

# creating the first decorator without the arguments
# a simple decorator
def first_decorator(func):
def wrapper_func():
print("code before calling the func")
result = func()
print("run the code after the func is called")
return result
return wrapper_func

def method():
return "outside method is called"

obj = first_decorator(method())
obj()

#this will provide the below results


# code running before calling the func
# code running after calling the func
# 'outside method is called'

But in the above code snippet we are calling the decorator ex[plicitly, to make it more readable and clean we’ll be using the pie syntax. That is

@first_decorator
def method():
return "outside method is called"

# the easy and clean implementation
obj2 = method()
obj2

#same above output will get displayed

Second example would be noticing the passing of parameters in functions and in decorators

def divide_number(func):
def inner(a,b):
print(f"I am going to divide {a} and {b}")
if b==0:
print("Wrong number entered thus can't divide!!")
return

return func(a,b)
return inner

@divide_number
def divide(a,b):
return a/b

divide(5,6)

#result would be
# I am going to divide 5 and 6
# 0.8333333333333334

With the examples provided, I hope you’ve gained a clearer understanding of how to utilize decorators and the ways they can dynamically enhance a function’s capabilities while preserving its core functionality.

Now, we’ll explore how to apply these decorators in practical analysis and within our data science projects. This exploration will offer you a broader perspective on leveraging Python’s functionalities to create code that’s not only cleaner but also more reusable.

# importing the libraries, an example using random data

# to use decorators for styling purposes that make the code more efficient
# this helps in automatically set themes or other configs of the plot

def set_style_plot(style):
def decorator(func):
def wrapper_func(*args,**kwargs):
original_style= plt.rcParams['figure.figsize']
plt.style.use(style)
figsize= kwargs.pop('figsize',(10,6)) # this is the default value that has been set
plt.figure(figsize=figsize)#the variable that we have define above the code
result = func(*args,**kwargs)
plt.style.use('default')
plt.rcParams['figure.figsize']=original_style
# with this after applying the functionality we are setting it to its default values
return result
return wrapper_func
return decorator


# using the above decorator on the method to create the plot

@set_style_plot("ggplot")
def create_plot(data, **kwargs):
plt.plot(data)
plt.show()

random_data = np.random.rand(100)
create_plot(random_data,figsize=(12,8))

# this will provides the below output
Output by Author

In our second example, we’ll delve into how decorators can be a game-changer in our routine data analysis and preprocessing tasks, specifically through caching/memoization. Why is this important? In data preprocessing, we often perform computations that are resource-intensive and time-consuming. To avoid repeating these expensive operations, we can use decorators to cache the results. Here’s how we can implement this strategy to efficiently compute the average value of a dataset, ensuring that once a computation is done, it’s stored and reused from cache memory whenever needed.

# we'll see how to use the decorators for caching/memoization. 
# why? because in data preprocessing, we perform expensive computation that we don't want to repeat
# so here is an example of getting the average value of the dataset we have. Thus every time it is not require to run the computation. Once it is done
# then we can use the cache memory.

def memoize(func):
cache ={}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args]=result
return result
return wrapper

@memoize
def expensive_processing(data):
time.sleep(2)
return sum(data)/len(data)

data1 = [1, 2, 3, 4, 5]
data2 = [4, 5, 6]


start_time = time.time()
result = expensive_processing(tuple(data1))
end_time = time.time()

print(f"first call for data1 {end_time - start_time:.2f} seconds, result: {result}")

# first call for data1 2.00 seconds, result: 3.0

start_time = time.time()
result = expensive_processing(tuple(data1))
end_time = time.time()

print(f"second call for data1 {end_time - start_time:.2f} seconds, result: {result}")
# second call for data1 0.00 seconds, result: 3.0

As we conclude this exercise on decorators, I want to extend a heartfelt thank you for joining me on this journey. To continue our learning adventure, I’ve created a GitHub repository where I’ll be sharing insights into Python programming, ranging from beginner to advanced topics, and I’ll be regularly updating it. This is an invitation for us to collaborate and enhance our Python skills together.

I highly encourage you to contribute your thoughts, experiences, and ways you implement decorators in your day-to-day tasks in the comments.

Let’s build a community that thrives on shared knowledge and growth. Please follow the repository for more insightful articles and updates.

Your input is invaluable in making this a resource for all of us to learn and grow together.

--

--