How to Implement Multiple Decorators in Python
- Implement a Decorator: Functions as First-Class Objects
- Implement a Parameterized Decorator in Python
-
Implement Decorator Using
@
in Python - Implement Multiple Decorators in Python
- Conclusion
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.