How to Implement Multiple Decorators in Python

Jay Shaw Feb 02, 2024
  1. Implement a Decorator: Functions as First-Class Objects
  2. Implement a Parameterized Decorator in Python
  3. Implement Decorator Using @ in Python
  4. Implement Multiple Decorators in Python
  5. Conclusion
How to Implement Multiple Decorators in Python

One of Python’s most predominant features is that we can use decorators to change the behavior of functions or classes. We can use decorators to make changes in a part of the program with codes already inside the program.

Decorators are lines of code in a program that change some part of that program during execution. The process of eliciting changes to a program during compilation is called metaprogramming.

In this article, the reader will go through the basics of decorators, i.e., how it is declared, implemented, and chained together in Python.

Implement a Decorator: Functions as First-Class Objects

Syntactically, we can declare decorators by passing a function as an iterable object to another. This is possible because everything in Python is a first-class object; thus, we can pass every Python construct as a parameter or assign it to a variable.

That means every class, function, and declared variable could be passed as objects. The below examples demonstrate this:

Code:

def func():
    def inner():
        print("Chocolate")

    return inner


taste = func()
taste()

Output:

"C:\Users\Win 10\main.py"
Chocolate

Process finished with exit code 0

Here, a nested function is created, where the parent function func() has an inner function inner(). The inner() function prints a statement and returns itself while inside a function.

The decorator function func() passes its data to an empty object function taste. Thus decorating it.

If this object function had any functionality, the decorator would have made changes to it as well. In the latter parts of this article, you will see how decorators are used for eliciting change to a function.

In Python, we can pass and return functions as arguments to other functions. A decorator can also accept a function as an argument and return results using this notion.

The example below demonstrates parameterized decorators. To understand it more easily, think of functions as real-world objects.

Implement a Parameterized Decorator in Python

We will present a bakery example to understand how decorators can take other functions as parameterized arguments.

Here, the bakery is a parameterized method that takes an object function obj_func() as a parameter. Inside this method, a nested function inner() is declared, which prints Dough.

After that, obj_func() is called, returning the inner() function. Calling the object function calls the function that is being decorated.

As you can closely observe, the bakery is a parameterized method that takes the argument obj_func(), which is nothing but the function wheat(), and calls it after the inner() function executes the print statement.

Code:

def inner():
    print("Dough")
    obj_func()


return inner

This function which ought to be decorated, i.e., wheat, has a print statement: Turned into bread.

Code:

def wheat():
    print("Turned into bread")

A new object function final is created that stores the decorated function.

The syntax object_function = decorator(decorated_function) decorates the function wheat() by passing it as an object to the parameterized method bakery, which implements the properties of the inner() function to it.

Code:

final = bakery(wheat)
final()

The decorated function is saved in the object function final. When compiled, the program executes the inner() function first, then calls obj_func(), which passes the object function wheat() and prints its contents.

Loosely put, wheat is converted into bread when placed inside a bakery, and the result is printed: Turned into bread. Just like how a bakery works in the real world!

Code:

def bakery(obj_func):
    def inner():
        print("Dough")
        obj_func()

    return inner


def wheat():
    print("Turned into bread")


final = bakery(wheat)
final()

Output:

"C:\Users\Win 10\main.py"
Dough
Turned into bread

Process finished with exit code 0

Implement Decorator Using @ in Python

This segment demonstrates how a function can be decorated using the syntax @function_name. In this example, a program is used which has:

  • A parameterized nested function;
  • An inner function that checks the values between variables x and y and swaps them if the numerator is smaller than the denominator;
  • A third function that gets decorated with the swapped values divides the two numbers and prints them.

The decorator function decor_func takes in an object function obj1 as its parameter. Inside, the inner function is created that swaps values if a larger number is provided in the denominator field.

Code:

def decor_func(obj1):
    def swap(x, y):
        pass

As the inner function swap parameters are the same as the function quot parameters, the swapped values stored inside obj1 are returned from the inner function, passing the changed values to the function quot before the compiler executes it.

The syntax @decor_func is declared above the function quot in the example. It tells the compiler to take the parameters of function obj1 and pass them to the function quot.

Code:

def decor_func(obj1):
    def swap(x, y):
        if x < y:
            temp = x
            x = x + y - x
            y = y + temp - y
        return obj1(x, y)

    return swap


# Syntax to Decorate function
@decor_func
def quot(x, y):  # Displays quotient of variable x/y
    print(x / y)


quot(2, 4)

Output:

"C:\Users\Win 10\main.py"
2.0

Process finished with exit code 0

Implement Multiple Decorators in Python

Chaining decorators is a technique to stack decorators on top of one another so that the target function gets decorated repeatedly, for the number of times @function_name is declared.

In the below program, two functions are created, decor and decor1. These functions are decorators and have an inner function, which performs arithmetic operations and returns the result.

To chain decorators, these must be defined together (on top of each other) above the function to be decorated. It must also be noted that the compiler reads decorators from bottom to top.

This means the decorator placed just above the function name gets implemented first, and the other decorators are implemented after that toward the top.

Code:

@decor  # Gets implemented second
@decor1  # Gets implemented first
def num():
    return 5

In the below example, the function num() returns a value to the decorator functions serially. At first, decor1 takes the value, passes it to the object function func(), and returns the altered value to num().

Similarly, this process is repeated with the other decorator function. Finally, when num() is printed, it produces 50 as the output.

Code:

# code for testing decorator chaining
def decor1(func):
    def inner():
        x = func()
        return x * x

    return inner


def decor(func):
    def inner():
        x = func()
        return 2 * x

    return inner


@decor
@decor1
def num():
    return 5


print(num())

Output:

"C:\Users\Win 10\main.py"
50

Process finished with exit code 0

Conclusion

This article provided a clear picture to the reader of how decorators are used in a program. The reader should learn how decorators can be used for a function, how parameters can be provided to a decorator, and how to chain multiple decorators.

Related Article - Python Decorator