Once we have learned about closures which is an important part of the decorator learning process, we can move on to the functionality of a decorator. We must know that Python treats everything in it as objects, even a function is an instance of a specific object. Decorator is a method which allows you to wrap a function within a function using functions as attributes and calling it from within. A decorator literally represents its own name because it decorates a specific function to return a special value covered by an outer value, just like a gift.
In technical terms, it is simply used to modify the behaviour or callbacks of an existing function, it uses a function as an argument to the nested function, closure takes non-local parameters as an input, decorators take in entire functions as arguments to another function. Let’s learn about the necessities before:
First lets see an example of how we can manipulate a function object and assign it different names as an identifier.
def plus1(x): #A simple function to return a multiplier
n = x * 5
return n #returns n as the resultant value
print(plus1(5)) #simply prints a called function
newobj = plus1 #assigning a new object to the function instance
print(newobj(60)) #The new obj takes in values, the same as the original object.
Here we can see that we have assigned a new object to the original function object, which takes in value as normal, the newobj object is an identifier for the plus1 function object. Let’s see how we can pass a function and return from another function:
def p1(x): #A simple multiplier function
return x * 2
def p2(x): #A simple division function
return x / 2
def opr(func, x): #An operator function which imports a function along with a variable to be worked on!
n = func(x) #Here it uses the x variable on whatever function we import to the opr function.
return n
print(opr(p1,6)) #Here we used p1 in opr
print(opr(p2,6)) #Here we used p2 in opr as the division function
iden1 = opr #Here we used a different identifier to relate to the original function
print(iden1(p1,25)) #Calling the identifier with the multiplier function and a multiplying value.
Here we can see that we can call a function within a function even with multiple parameters to be used inside of the called function, it allows for greater flexibility and we will see how it contributes to a decorator later on!
A decorator, like it was discussed before, acts like a closure but in a way which can be termed as a double call, you can call the inner function with the non-local variable in the variable function but the original variable value is saved even though the variable value is changed during the second call. A modifying closure can also be called a decorator:
def stat1(func): #this is a closure
def stat2(): #This inner function contains the actual "gift" of the decorator
print("decorator")
func() #The function is called, this will use the actual function we will provide to the new function object
return stat2 #this returns the actual decorator function
def norm(): #this is the function which will be decorated
print("the normal function or the gift ")
deco1 = stat1(norm) #Similar to a closure, we have defined a function object for the decorator
deco1() #Calling the decorator function
The above mentioned code is an example of a decorator without parameters, we can use multiple parameters along with the func parameter in the parent and normal functions.
def mul1(func): #A simple closure with a function call as a parameter
def mul2(a, b): #Child function that uses two separate parameters same as norm
print("In this function will divide", a, "and", b)
return func(a, b) #Returns the function on whom the decorator will be called
return mul2 #returns the inner function just like a closure
@mul1 #The @ refers to a decorator by the name of mul1, using @ avoids the need of creating a special function object to call a decorator
def norm(a, b):
n = a * b
print(n)
norm(50,40) #A simple call on the normal function which will induce the decorator function.