--> Nikola Dakić - Blog About Software Engineering, DevOps and AI – Decorators

Decorators

Posted on Fri 29 September 2023 in SWE

Hey there, fellow Pythonistas! 🐍

Today, we're going to dive into one of Python's coolest and somewhat mysterious features: decorators. If you've heard the term thrown around in Python circles and thought, "What on earth is that?", you're in the right place. Decorators are like the secret sauce of Python programming, and once you get the hang of them, they'll make your code more elegant and maintainable.

So, what's a decorator?

In Python, a decorator is a function that takes another function and extends or modifies its behavior without changing its source code. Think of it as a way to "decorate" or "wrap" a function with some extra functionality. You can use decorators to add features like logging, authentication, caching, timing, and more to your functions.

How do decorators work?

The magic of decorators lies in Python's function nesting. In Python, functions are first-class objects, which means they can be passed as arguments to other functions. Actually, everything in Python is an object and functions are no exception. You can assign a function to a variable, pass it as an argument to another function, or even return it from a function.
The concept of decorators is not unique to Python, but it's one of the features that make Python so powerful and fun to use.

Let's look at a simple example of a decorator in action:

import time
from functools import wraps


def timing(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        finish = time.perf_counter()
        print(f"Finished in {round(finish - start, 2)} seconds")
        return result

    return wrapper


@timing
def do_calculation():
    time.sleep(2)  # Simulate some calculation
    print("Calculations finished!")


if __name__ == "__main__":
    do_calculation()

In thе example above, we defined a decorator called timing that takes a function as an argument and returns a new function. The new function is called wrapper, and it's the one that will extend/wrap the behavior of the original function. The wrapper function takes any number of positional and keyword arguments, which is why we use *args and **kwargs. Inside the wrapper function, we start a timer, call the original function, and then stop the timer. Finally, we print the time it took for the function to execute and return the result of the original function.

Beside the timing decorator, we also defined a function called do_calculation that simulates some calculation by sleeping for 2 seconds and then prints "Calculations finished!". To apply the timing decorator to the do_calculation function, we use the @ symbol followed by the name of the decorator. That's it! Now, when we call the some_calculation function, the timing decorator will be applied to it, and the output will look like this:

Calculations finished!
Finished in 2.01 seconds

From the result above, we can see that timing logic was added to the do_calculation function without changing its source code. This is the power of decorators!

Real-World Use Cases

Decorators are incredibly versatile and can be used for various purposes. Some common use cases include:

  • Logging: As mentioned earlier, you can log information like function calls and execution times with decorators.
  • Authentication and Authorization: You can use decorators to ensure that certain functions are only accessible to authenticated users or those with specific permissions.
  • Caching: Decorators can cache the results of expensive function calls, improving performance.
  • Error Handling: You can create decorators to handle exceptions and errors gracefully, making your code more robust.
  • Timing and Profiling: Decorators can be used to profile functions and measure their execution time.
  • Route Handling (Web Frameworks): In web development, decorators are widely used to define routes and middleware in frameworks like Flask and Django.

Now when we know how decorators work, don't hesitate to use them in your code.
They are a great way to keep your code DRY (Don't repeat yourself) and make it more readable and maintainable.

Full code and results of the execution can be found here.

I hope you find this article useful.
If you would like to get more content like this directly to your inbox, consider subscribing to my newsletter.

Happy coding, and thanks for reading! 🚀🐍