Python Decorators: For Code Reusability and Modularity

a comprehensive guide to python decorators

Introduction

Python decorators are a powerful feature of the language that allows you to modify the behavior of functions or classes without changing their source code. Decorators provide a way to add functionality to existing code in a clean and modular way, making it easy to reuse code across different parts of your program.

A decorator is a function that takes another function as its argument and returns a new function that wraps the original function, adding some extra behavior or functionality. Decorators are used extensively in Python’s standard library, as well as in many popular third-party libraries and frameworks.

Define a decorator

To define a decorator, you simply define a function that takes another function as an argument and returns a new function that performs some additional processing before or after the original function is called. Below is an illustration of a basic decorator that introduces a β€œHello, World!” message before a function is called:


def hello_decorator(func):
    def wrapper():
        print("Hello, World!")
        func()
    return wrapper

@hello_decorator
def my_function():
    print("This is my function.")

In this example, the β€˜hello_decoratorβ€˜ function takes another function (func) as an argument and returns a new function (wrapper) that first prints β€œHello, World!β€œ, and then calls the original function (func). The β€˜@hello_decoratorβ€˜ syntax is used to apply the decorator to the my_function function.

When we call β€˜my_function()β€˜, it will first print β€œHello, World!β€œ, and then print β€œThis is my function.” This is because the β€˜my_functionβ€˜ function has been modified by the β€˜hello_decoratorβ€˜ decorator to add additional functionality.

Decorators with arguments

Decorators can also take arguments, which can be used to modify their behavior. Here’s an example of a decorator that takes an argument:


def repeat_decorator(num_repeats):
    def decorator(func):
        def wrapper():
            for i in range(num_repeats):
                func()
        return wrapper
    return decorator

@repeat_decorator(num_repeats=3)
def my_function():
    print("This is my function.")

In this example, the β€˜repeat_decoratorβ€˜ function takes an argument (num_repeats) and returns a decorator function that takes another function (func) as an argument and returns a new function (wrapper) that calls the original function multiple times (num_repeats times).

When we apply this decorator to the my_function function with β€˜@repeat_decorator(num_repeats=3)β€˜, calling β€˜my_function()β€˜ will print β€œThis is my function.” three times.

Example 1

One of the most common uses of decorators is to add additional functionality to functions, such as logging or caching. For example, let’s say you have a function that performs some expensive computation and you want to cache its results to avoid repeating the computation. You could use a decorator to wrap the function and cache its results:


import functools

def cache(func):
    cache = {}

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        key = (args, frozenset(kwargs.items()))
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]

    return wrapper

@cache
def expensive_computation(x, y):
    # do some expensive computation here
    return result

In this example, the β€˜cacheβ€˜ decorator takes a function as an argument and returns a new function that wraps the original function in a caching mechanism. The decorator creates a dictionary called β€˜cacheβ€˜ to store the results of each function call, keyed by the function arguments. If a given set of arguments has been seen before, the cached result is returned immediately, without re-computing the function. Otherwise, the original function is called to compute the result, which is then cached for future calls.

Example 2

Another common use of decorators is to add additional behavior to classes. For example, let’s say you have a class that represents a database connection, and you want to ensure that the connection is closed automatically when the class is no longer needed. You could use a decorator to add a context manager to the class:


def auto_close(cls):
    class Wrapper:
        def __init__(self, *args, **kwargs):
            self._wrapped = cls(*args, **kwargs)

        def __getattr__(self, name):
            return getattr(self._wrapped, name)

        def __enter__(self):
            return self._wrapped.__enter__()

        def __exit__(self, exc_type, exc_val, exc_tb):
            self._wrapped.__exit__(exc_type, exc_val, exc_tb)
            self._wrapped.close()

    return Wrapper

@auto_close
class DatabaseConnection:
    def __init__(self, host, port, username, password):
        self._conn = connect(host, port, username, password)

    def query(self, sql):
        # execute a SQL query and return the results
        pass

    def close(self):
        self._conn.close()

In this example, the β€˜auto_closeβ€˜ decorator takes a class as an argument and returns a new class that wraps the original class in a context manager. The wrapper class forwards all attribute access and method calls to the wrapped class, but adds an β€˜__enter__β€˜ and β€˜__exit__β€˜ method that automatically closes the connection when the context manager exits.

Summary

Decorators are versatile and potent characteristics of Python that enable you to alter the functionality of functions and classes without making modifications to their original source code.

Decorators provide a way to add functionality to existing code in a clean and modular way, making it easy to reuse code across different parts of your program. If you’re not already using decorators in your Python code, you should definitely give them a try!

Share your love
Subhankar Rakshit
Subhankar Rakshit

Hey there! I’m Subhankar Rakshit, the brains behind PySeek. I’m a Post Graduate in Computer Science. PySeek is where I channel my love for Python programming and share it with the world through engaging and informative blogs.

Articles:Β 194