Master LLMs with our FREE course in collaboration with Activeloop & Intel Disruptor Initiative. Join now!

Publication

Simplifying Decorators in Python | A step-by-step approach.
Latest   Machine Learning

Simplifying Decorators in Python | A step-by-step approach.

Last Updated on January 29, 2024 by Editorial Team

Author(s): Akhil Theerthala

Originally published on Towards AI.

DALL.E generated image for the blog post of title “Simplifying decorators in python programming language”. Image generated by Author

Simplifying Decorators in Python U+007C A step-by-step approach.

Python is one of the simpler languages that we can learn these days. However, despite being the simplest one, it still has a few areas that need focused attention to be correctly understood. One such topic is the use of decorators. When I first started learning about it, it was not directly clear on what this meant. However, after repeated encounters with the decorator, I finally took some time to understand the mechanism of a decorator.

Imagine this scenario. You are working on a project where you have to add all the numbers up to a real number k as fast as possible. Now, there are multiple methods to do this. You can keep adding all the numbers through a for loop. You can directly use the expression for the sum of first n natural numbers. Or you can try to come up with something else.

def add_directly(k):
result = 0
for i in range(k):
result+=i+1
return result

def use_expression(k):
return k*(k+1)/2

Now both of these functions get our job done. However, if you recall, we need a method that is fastest. Now, in order to achieve this, we need to find the time each of these functions takes. You can directly run these functions two times and print the time each of the functions takes.

start = time.time()
print(add_directly(30000))
end = time.time()
print(f’Time taken by adding directly: {end-start} \n’)

start = time.time()
print(use_expression(30000))
end = time.time()
print(f’Time taken by the expression: {end-start} \n’)

If you noticed, this violates the common rule DRY (Don’t repeat yourself). So, what can we do? We can write a new function whose purpose is to evaluate the time taken by a different function.

def evaluate_time(func):
"""
Evaluates the time taken for a function to run.
Args:
func (function): any generic function
"""

k = 300000
start = time.time()
print(f’Function Output: {func(k)}
end = time.time()
return end-start

Now, when you want the time taken by each function, you just use the evaluate_time function that we previously defined. We are now close to the final meaning of a decorator, but not quite there yet.

Now that we are clear with the intent behind wrapping a function, we need to tidy things up. If we want to run the wrapped function, then we need to run it like this, evaluate_time(add_directly). When we want to test the wrap for tens or sometimes hundreds of functions, we need to keep repeating this wrapper(func)every time. This is once again contradicting the DRY rule. So, what do we do?

Once again, we define a function to wrap a different function. This function is what we call a decorator.

def time_decorator_function(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'Time taken {func.__name__}: {end-start}')
return result

return wrapper


@time_decorator_function
def add_directly(k):
result = 0
for i in range(k):
result+=i+1
return result

@time_decorator_function
def use_expression(k):
return k*(k+1)/2

What happened here?

Well, we have basically wrapped the functions with the additional lines of code, so that every time we run the add_directly function, we print the time it took to run. i.e., we changed the add_directly to a wrapper which contains the previous version of the add_directly. This is very similar to how we set x=x+1, i.e. just updating the value stored in x.

This is what the outputs look like now,

outputs of executing above code.

With the basic understanding, we can now add all the personal knowledge, like defining attributes for the wrapper and other things to play around with when needed. People tend to use decorators for multiple purposes like caching, logging, unit tests, authentication/authorization (in Flask and Django), etc.

It is now in your hands to play around and decorate whatever functions you find useful. However, I just want to add another cherry to what we have discussed so far. Remember what I said about decorators being similar to x=x+1? Well, they truly are similar, and this process loses all the metadata (like doc_strings) we add to the function.

See it for yourself by running the code below:


def time_decorator_function(func):
def wrapper(*args, **kwargs):
"""
This just wraps over the input func
"""

start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f’Time taken {func.__name__}: {end-start}’)
return result
return wrapper

@time_decorator_function
def add_directly(k):
"""
Adds the numbers between 1 and k directly.
"""

result = 0
for i in range(k):
result+=i+1
return result


@time_decorator_function
def use_expression(k):
return k*(k+1)/2

print(add_directly.__name__)
print(add_directly.__doc__)

To retain the metadata, we often use the functools.wraps decorator on our wrapper function, which adds the original metadata to the wrapper function. For example, see the code block below,


import functools
def custom_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
pass
return wrapper

Now, as homework, try adding the decorator by yourself and see the attributes yourself.

— –

With this understanding as the foundation, you can now play around with the different forms of decorators and keep writing wonderful code.

With this understanding as the foundation, you can now play around with the different forms of decorators and keep writing wonderful code. Let’s meet again with another simple concept that we often get confused with and discuss it. If you like such articles, subscribe to Neuronuts to get notified as soon as a new one is released. You can also follow Neuronuts on Instagram and LinkedIn to stay connected.

Thanks for reading! Let’s meet next time!U+270C️

Originally published at https://www.neuronuts.in.

Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.

Published via Towards AI

Feedback ↓