Learn Python – Python Decorator- Basic and advance

Decorators are one of the most beneficial and effective tools of Python. These are used to alter the conduct of the function. Decorators grant the flexibility to wrap some other feature to make bigger the working of wrapped function, without permanently modifying it.

In Decorators, features are surpassed as an argument into every other feature and then called interior the wrapper function.

It is also known as meta programming the place a part of the software tries to exchange any other section of application at bring together time.

Before understanding the Decorator, we need to recognize some vital ideas of Python.

What are the functions in Python?

Python has the most fascinating characteristic that the whole thing is dealt with as an object even lessons or any variable we outline in Python is also assumed as an object. Functions are satisfactory objects in the Python due to the fact they can reference to, exceeded to a variable and returned from other functions as well. The example is given below:

def func1(msg):  
    print(msg)  
func1("Hii")  
func2 = func1  
func2("Hii")  

Output:

Hii
Hii

In the above program, when we run the code it give the equal output for both functions. The func2 referred to feature func1 and act as function. We want to apprehend the following concept of the function:

The function can be referenced and passed to a variable and returned from other functions as well.

The functions can be declared inside another function and passed as an argument to another function.

Inner Function

Python gives the facility to define the feature interior another function. These kinds of functions are known as inner functions. Consider the following example:

def func():  
     print("We are in first function")  
     def func1():  
           print("This is first child function")  
     def func2():  
           print(" This is second child function")  
     func1()  
     func2()  
func()  

Output:

We are in first function
This is first child function
This is second child function

In the above program, it would not depend how the baby features are declared. The execution of the infant characteristic makes effect on the output. These toddler features are domestically bounded with the func() so they can’t be known as separately.

A function that accepts other function as an argument is also referred to as higher order function. Consider the following example:

def add(x):  
    return x+1  
def sub(x):  
    return x-1  
def operator(func, x):  
    temp = func(x)  
    return temp  
print(operator(sub,10))  
print(operator(add,20))  

Output:

9
21

In the above program, we have surpassed the sub() characteristic and add() feature as argument in operator() function.

A characteristic can return any other function. Consider the below example:

def hello():  
    def hi():  
        print("Hello")  
    return hi  
new = hello()  
new()  

Output:

Hello

In the above program, the hi() characteristic is nested inner the hello() function. It will return each time we name hi().

Decorating functions with parameters

Let’s have an instance to understand the parameterized decorator function:

def divide(x,y):  
    print(x/y)  
def outer_div(func):  
    def inner(x,y):  
        if(x<y):  
            x,y = y,x  
           return func(x,y)  
     return inner  
divide1 = outer_div(divide)  
divide1(2,4)  

Output:

2.0

Syntactic Decorator

In the above program, we have adorned out_div() that is little bit bulky. Instead of the use of above method, Python approves to use decorator in convenient way with @symbol. Sometimes it is known as “pie” syntax.

def outer_div(func):  
    def inner(x,y):  
        if(x<y):  
           x,y = y,x  
          return func(x,y)  
     return inner  
# syntax of generator  
@outer_div  
def divide(x,y):  
     print(x/y)  

Output:

2.0

Reusing Decorator

We can reuse the decorator as nicely by means of recalling that decorator function. Let’s make the decorator to its personal module that can be used in many other functions. Creating a file known as mod_decorator.py with the following code:

def do_twice(func):  
    def wrapper_do_twice():  
        func()  
        func()  
    return wrapper_do_twice  

We can import mod_decorator.py in other file.

from decorator import do_twice  
@do_twice  
def say_hello():  
    print("Hello There")  
say_hello()  

Output:

Hello There
Hello There

Python Decorator with Argument

We desire to pass some arguments in function. Let’s do it in following code:

from decorator import do_twice  
@do_twice  
def display(name):  
     print(f"Hello {name}")  
display()  

Output:

TypeError: display() missing 1 required positional argument: 'name'

As we can see that, the characteristic did not receive the argument. Running this code raises an error. We can restore this error by using *args and **kwargs in the inner wrapper function. Modifying the decorator.py as follows:

def do_twice(func):  
    def wrapper_function(*args,**kwargs):  
        func(*args,**kwargs)  
        func(*args,**kwargs)  
   return wrapper_function  

Now wrapper_function() can accept any variety of argument and bypass them on the function.

from decorator import do_twice  
@do_twice  
def display(name):  
      print(f"Hello {name}")  
display("John")  

Output:

Hello John
Hello John

Returning Values from Decorated Functions

We can manipulate the return type of the adorned function. The instance is given below:

from decorator import do_twice  
@do_twice  
def return_greeting(name):  
     print("We are created greeting")  
     return f"Hi {name}"  
hi_adam = return_greeting("Adam")  

Output:

We are created greeting
We are created greeting

Fancy Decorators

Let’s recognize the fancy decorators by using the following topic:

Class Decorators

Python provides two methods to decorate a class. Firstly, we can enhance the approach internal a class; there are built-in decorators like @classmethod, @staticmethod and @property in Python. The @classmethod and @staticmethod define techniques inner category that is now not related to any different occasion of a class. The @property is typically used to modify the getters and setters of a category attributes. Let’s recognize it by way of the following example:

Example: 1- @property decorator – By the usage of it, we can use the category function as an attribute. Consider the following code:

class Student:  
    def __init__(self,name,grade):  
         self.name = name  
         self.grade = grade  
    @property  
    def display(self):  
         return self.name + " got grade " + self.grade  
  
stu = Student("John","B")  
print("Name:", stu.name)  
print("Grade:", stu.grade)  
print(stu.display)  

Output:

Name: John
Grade: B
John got grade B

Example:2 – @staticmethod decorator- The @staticmethod is used to define a static technique in the class. It is known as by way of the usage of the category name as well as instance of the class. Consider the following code:

class Person:  
     @staticmethod  
     def hello():  
          print("Hello Peter")  
per = Person()  
per.hello()  
Person.hello()  

Output:

Hello Peter
Hello Peter

Singleton Class

A singleton type solely has one instance. There are many singletons in Python consisting of True, None, etc.

Nesting Decorators

We can use multiple decorators with the aid of using them on top of each other. Let’s reflect onconsideration on the following example:

@function1  
@function2  
def function(name):  
      print(f "{name}")  

In the above code, we have used the nested decorator through stacking them onto one another.

Decorator with Arguments

It is continually beneficial to omit arguments in a decorator. The decorator can be performed quite a few times according to the given value of the argument. Let us think about the following example:

Import functools  
  
def repeat(num):  
  
#Creating and returning a wrapper function  
    def decorator_repeat(func):  
        @functools.wraps(func)  
        def wrapper(*args,**kwargs):  
            for _ in range(num):  
                value = func(*args,**kwargs)  
             return value  
          return wrapper  
    return decorator_repeat  
  
#Here we are passing num as an argument which repeats the print function  
@repeat(num=5)  
def function1(name):  
     print(f"{name}")  

Output:

JavatPoint
JavatPoint
JavatPoint
JavatPoint
JavatPoint

In the above example, @repeat refers to a characteristic object that can be called in some other function. The @repeat(num = 5) will return a characteristic which acts as a decorator.

The above code may also appear complicated but it is the most typically used decorator pattern where we have used one extra def that handles the arguments to the decorator.

Note: Decorator with argument is no longer frequently used in programming, but it presents flexibility. We can use it with or without argument.

Stateful Decorators

Stateful decorators are used to maintain music of the decorator state. Let us consider the example the place we are growing a decorator that counts how many times the function has been called.

Import functools  
  
def count_function(func):  
@functools.wraps(func)  
def wrapper_count_calls(*args, **kwargs):  
wrapper_count_calls.num_calls += 1  
  
print(f"Call{wrapper_count_calls.num_calls} of {func.__name__!r}")  
return func(*args, **kwargs)  
  
wrapper_count_calls.num_calls = 0  
return wrapper_count_calls  
  
@count_function  
def say_hello():  
print("Say Hello")  
  
say_hello()  
say_hello()  

Output:

Call 1 of 'say_hello'
Say Hello
Call 2 of 'say_hello'
Say Hello

In the above program, the nation represented the range of calls of the feature saved in .num_calls on the wrapper function. When we call say_hello() it will show the wide variety of the call of the function.

Classes as Decorators

The lessons are the first-rate way to maintain state. In this section, we will learn how to use a class as a decorator. Here we will create a classification that incorporates __init__() and take func as an argument. The classification desires to be callable so that it can stand in for the decorated function.

To making a category callable, we implement the one-of-a-kind __call__() method.

import functools  
  
class Count_Calls:  
def __init__(self, func):  
functools.update_wrapper(self, func)  
self.func = func  
self.num_calls = 0  
  
def __call__(self, *args, **kwargs):  
self.num_calls += 1  
print(f"Call{self.num_calls} of {self.func.__name__!r}")  
return self.func(*args, **kwargs)  
  
@Count_Calls  
def say_hello():  
print("Say Hello")  
  
say_hello()  
say_hello()  
say_hello()  

Output:

Call 1 of 'say_hello'
Say Hello
Call 2 of 'say_hello'
Say Hello
Call 3 of 'say_hello'
Say Hello

The __init__() method shops a reference to the feature and can do any other required initialization.