Summary of the role of @ decorator in Python

  • 2021-12-13 08:51:07
  • OfStack

In the process of programming, we often encounter this requirement:

For example, I want to develop a calculator. I have written a bunch of functions for performing various calculations. Then we need to check the input data before executing various calculation functions to ensure that they must be numeric values before allowing functions to be executed, not strings;

For another example, I want to write a module for calculating the perimeter, area and certain angle of a triangle, and I have written several functions for calculation. Then, before performing the calculation, I must first ensure that the input three side lengths can form a triangle, and then the calculation is meaningful;

For another example, I want to develop a certain network application and write some functions to realize some operations of users. Then, I have to check and confirm that the user has logged in before I can perform these operations.

These requirements, to sum up, are that before executing the main function, it is often necessary to execute a pre-function and do some validation and other operations.

This kind of requirement is very common, and it is also an important measure to ensure the integrity and robustness of the program. So, what is easier to do?

You will say, this is very simple ah, write if statement in each function. Take that calculator, if we want to write addition, subtraction, multiplication and division, we can do this:


def plus(a,b):
    if type(a)==type(0) and type(b)==type(0): # Assuming that the calculator can only calculate integers, if you want to calculate decimals, then or type(0.0)
        return a+b
    else:
        print('Type must be number') # If the data type is wrong, the alarm will be output first, and the function value will be returned None
        return None

def minus(a,b):
    if type(a)==type(0) and type(b)==type(0):
        return a-b
    else:
        print('Type must be number')
        return None

def multiply(a,b):
    if type(a)==type(0) and type(b)==type(0):
        return a*b
    else:
        print('Type must be number')
        return None

def divide(a,b):
    if type(a)==type(0) and type(b)==type(0):
        return a/b
    else:
        print('Type must be number')
        return None

Well, direct violence. However, there are only 4 functions here. If you develop a calculator with several hundred functions, each function should be covered with if statements, which should not be troublesome.

So how to make it 1 point simpler? If you are smart, you must have thought of it. We can define a function for that judgment if separately, and then put the function for calculation in it, just like this:


def check(a,b,func): # Define the check function, and the variables are the parameters to be tested a,b And the function executed after the detection passes func
    if type(a)==type(0) and type(b)==type(0):
        return func(a,b)
    else:
        print('Type must be number')
        return None

def plus(a,b):
    return a+b

def minus(a,b):
    return a-b
...

# Main program 
check(1,2,plus) # Calculation 1+2
check(1,2,minus) # Calculation 1-2
check(1,2,multiply) # Calculation 1*2
check(1,2,divide) # Calculation 1/2

There is one point and one must pay special attention to it. The check (1, 2, plus) of the main program passes the plus function itself as a variable to check, and the check function determines how to execute the plus function. It cannot be written as check (1, 2, plus (1, 2)) here, and plus cannot take parameters and brackets.

This program is concise, addition, subtraction, multiplication and division functions only need to define their own operations, variable detection to the check function. It is also easier to understand this way.

But this is not the case for users who use this program, and they will find it very ugly to write like this.

Why? I want to take the program to do addition, subtraction, multiplication and division calculation, but no matter what I calculate, every time I use check this function!

Is there any way to look good and concise? The decorator has played this magical role.

The above requirement can be written as follows with decorators:


def check(func):
    ...

@check
def plus(a,b):
    return a+b

@check
def minus(a,b):
    return a-b

...

# Main program 
plus(1,2) # Calculation 1+2
minus(1,2) # Calculation 1-2
...

First, feel it intuitively. Through @ check, check function is "injected" into plus function, which makes plus function have the function of parameter detection. In this way, in the main program, if you want to calculate the addition, you can call plus directly, and you can check it first and then calculate it.

So, what is the definition of this decorator check? Let's take a look.


def check(func): # Define decorators check
    def newfunc(a,b): # Define function templates, that is, how to handle func
        if type(a)==type(0) and type(b)==type(0):
            return func(a,b)
        else:
            print('Type must be number!')
            return None
    return newfunc # Will process the func As a new function newfunc Output 

@check
def plus(a,b):
    return a+b

# Main program, calculation 1+2
plus(1,2)

We can see that when the decorator @ check acts on the plus function, the plus function itself is passed into the decorator as an argument func. Within the definition of the decorator check, a function template is defined to describe how to handle the input func. It can be seen that newfunc applies the if statement for judging the data type to func (that is, the input plus), and finally outputs the set newfunc instead of the original func. In this way, executing func is executing newfunc, and executing plus is executing a new function with if statement.

Therefore, through the decorator, the new function with the judgment statement replaces the original plus function, but it is still called through the function name plus, so it seems that the plus function is "decorated".

Of course, if you search on the Internet, you will see a more standardized version of how to define decorators. It seems more difficult to understand 1, but it is actually 1:


def checkall(func):
    def wrapper(*args,**kwargs):
        if type(args[0])==type(0) and type(args[1])==type(0):
            return func(*args,**kwargs)
        else:
            print('Type must be number!')
            return None
    return wrapper

Template function 1 is used to use wrapper to express, this is nothing, it is recommended that everyone write this way and standardize 1.

Parameter 1 is usually represented by * args, **kwargs of indefinite length, which may confuse some people. Because there may be many kinds of decorated functions, the number of parameters 1 is generally uncertain. Then * args, **kwargs is something? It doesn't matter what the English letters of args and kwargs are, but the key is the single asterisk * and double asterisk * * in front of them.

If I define a function, I can't be sure how many parameters there are, for example, I want to add a set of inputted numbers. Then plus (* x) can be defined. When this function is called, if multiple variables plus (1, 2, 3) are input, the input variables will be combined into a progenitor x= (1, 2, 3) input. Defining the double asterisk plus (**x) means that if the formal parameter plus (a=1, b=2, c=3) is written when the function is called, the input variables will be combined into a dictionary x= {a: 1, b: 2, c: 3} passed into the function.

Of course, it can also be operated in reverse. When defining the function, the number of parameters is clear plus (a, b, c), so when calling the function, add the asterisk plus (* (1, 2, 3)), that is, the input ancestor (1, 2, 3) is exploded and converted to plus (1, 2, 3)

What's the use of writing like this in the decorator? Let's take a closer look at newfunc (a, b) we wrote before, which means that the new function has two parameters a and b. What if the decorated original function has three parameters? Isn't it useless?

Let's look at what someone else wrote. It uses wrapper (* args, **kwargs) in definition, that is, no matter how many parameters there are, package and enter wrapper. In wrapper, when the original function is called, func (* args, **kwargs) is used, that is, the input ancestor is unpacked and then passed into func. Although it seems that nothing has been done, it does adapt to the uncertain function parameters, so that the decorator can decorate a variety of functions with different numbers of parameters.

Let's do that first.


Related articles: