#Raise your hand to learn Python# Python intermediate knowledge decorator

Function decorator

In Python, Decorators are mainly used to modify the function of the function, and the modification premise is to change the original function code. The decorator will return a function object. Therefore, in some places, the decorator will be called "function of the function".
There is also a design pattern called "Decorator Pattern", which will be covered in this subsequent course.

When calling the decorator, use @, which is a programming syntax sugar provided by Python. After using it, your code will look more Python.

Basic use of decorator

When learning decorators, the most common case is to count the running time of a function. Next, I'll share it with you.
Calculate function run time:

import time

def fun():
    i = 0
    while i < 1000:
        i += 1
def fun1():
    i = 0
    while i < 10000:
        i += 1
s_time = time.perf_counter()
fun()
e_time = time.perf_counter()
print(f"function{fun.__name__}The running time is:{e_time-s_time}")

If you want to add call time to each correspondence, the workload is huge. You need to repeatedly modify the internal code of the function or the code of the function call location. In this demand, decorator syntax appears.

Let's take a look at the first modification method. This method does not add a decorator, but writes a general function, which completes the reusability of the code by using the feature that functions can be used as parameters in Python.

import time
def fun():
    i = 0
    while i < 1000:
        i += 1

def fun1():
    i = 0
    while i < 10000:
        i += 1

def go(fun):
    s_time = time.perf_counter()
    fun()
    e_time = time.perf_counter()
    print(f"function{fun.__name__}The running time is:{e_time-s_time}")

if __name__ == "__main__":
    go(fun1)

Next, this technique is extended to the decorator syntax in Python. The specific modifications are as follows:

import time

def go(func):
    # The wrapper function name here can be any name
    def wrapper():
        s_time = time.perf_counter()
        func()
        e_time = time.perf_counter()
        print(f"function{func.__name__}The running time is:{e_time-s_time}")
    return wrapper

@go
def func():
    i = 0
    while i < 1000:
        i += 1
@go
def func1():
    i = 0
    while i < 10000:
        i += 1

if __name__ == '__main__':
    func()

In the above code, pay attention to the go function. Its parameter func is a function and the return value is an internal function. After executing the code, it is equivalent to injecting the calculation time code into the original function. In the code calling part, you do not make any changes, and the function func has more functions (the function of calculating running time).

The decorator function successfully expands the function of the original function without modifying the original function code. After learning this case, you have a preliminary understanding of the decorator.

Decorate functions with parameters

Look directly at the code to learn how to decorate functions with parameters:

import time

def go(func):
    def wrapper(x, y):
        s_time = time.perf_counter()
        func(x, y)
        e_time = time.perf_counter()
        print(f"function{func.__name__}The running time is:{e_time-s_time}")
    return wrapper

@go
def func(x, y):
    i = 0
    while i < 1000:
        i += 1
    print(f"x={x},y={y}")

if __name__ == '__main__':
    func(33, 55)

If you look dizzy, I'll mark the key transfer process of parameters for you.

In another case, the decorator itself has parameters, such as the following codes:

def log(text):
    def decorator(func):
        def wrapper(x):
            print('%s %s():' % (text, func.__name__))
            func(x)
        return wrapper
    return decorator

@log('implement')
def my_fun(x):
    print(f"I am my_fun Function, my arguments {x}")

my_fun(123)

When writing the decorator function in the above code, another layer of function is nested outside the decorator function. The running sequence of the final code is as follows:

my_fun = log('implement')(my_fun)

At this point, if we summarize, we can draw a conclusion: using a decorator with parameters wraps a function outside the decorator, uses the function to receive parameters and returns a decorator function.
It should also be noted that the decorator can only receive one parameter and must be a function type.
! [decorator of intermediate knowledge of python, snowball learning Python]( https://img-blog.csdnimg.cn/20210307141505987.png#pic\_center =300x)

Multiple decorators

Copy the following code first, and then study and research.

import time

def go(func):
    def wrapper(x, y):
        s_time = time.perf_counter()
        func(x, y)
        e_time = time.perf_counter()
        print(f"function{func.__name__}The running time is:{e_time-s_time}")
    return wrapper

def gogo(func):
    def wrapper(x, y):
        print("I'm the second decorator")
    return wrapper

@go
@gogo
def func(x, y):
    i = 0
    while i < 1000:
        i += 1
    print(f"x={x},y={y}")

if __name__ == '__main__':
    func(33, 55)

After the code is run, the output result is:

I'm the second decorator
 function wrapper Run time is: 0.0034401339999999975

Although multiple decorators are very simple to use, problems also arise. The running result of the code print(f"x={x},y={y}") is lost. Here, the execution sequence of multiple decorators is involved.

First explain the decoration sequence of the decorator.

import time
def d1(func):
    def wrapper1():
        print("Decorator 1 starts decorating")
        func()
        print("Finisher 1 finish")
    return wrapper1

def d2(func):
    def wrapper2():
        print("Decorator 2 starts decorating")
        func()
        print("Finisher 2 finish")
    return wrapper2

@d1
@d2
def func():
    print("Decorated function")

if __name__ == '__main__':
    func()

The result of running the above code is:

Decorator 1 starts decorating
 Decorator 2 starts decorating
 Decorated function
 Finisher 2 finish
 Finisher 1 finish

You can see very symmetrical output, and prove that the decorated function is at the innermost layer. The code converted into function call is as follows:

d1(d2(func))

In this part, you should note that the statements between the outer and inner functions of the decorator are not decorated on the objective function, but additional operations when loading the decorator.
When decorating a function, the code between the outer function and the inner function will be run.

The test results are as follows:

import time

def d1(func):
    print("I am d1 Code between internal and external functions")
    def wrapper1():
        print("Decorator 1 starts decorating")
        func()
        print("Finisher 1 finish")
    return wrapper1

def d2(func):
    print("I am d2 Code between internal and external functions")
    def wrapper2():
        print("Decorator 2 starts decorating")
        func()
        print("Finisher 2 finish")
    return wrapper2

@d1
@d2
def func():
    print("Decorated function")

After running, you can find that the output results are as follows:

I am d2 Code between internal and external functions
 I am d1 Code between internal and external functions

The d2 function runs before the d1 function.

Next, let's review the concept of decorator:
The name of the decorated function is passed to the decorated function as an argument.
After the decorated function executes its own internal code, it assigns its return value to the decorated function.

In this way, the operation process of the code above is as follows: after d1(d2(func)) executes d2(func), the original func function name will point to wrapper2 function, and after d1(wrapper2) function is executed, the wrapper2 function name will point to wrapper1. Therefore, when the last func is called, the code has been switched to the following content.

# First step
def wrapper2():
     print("Decorator 2 starts decorating")
     print("Decorated function")
     print("Finisher 2 finish")

# Step 2
print("Decorator 1 starts decorating")
wrapper2()
print("Finisher 1 finish")

# Step 3
def wrapper1():
    print("Decorator 1 starts decorating")
    print("Decorator 2 starts decorating")
    print("Decorated function")
    print("Finisher 2 finish")
    print("Finisher 1 finish")

The code after the third step above is just consistent with our code output.

Now go back to the case at the beginning of this section, why the output data is lost.

import time

def go(func):
    def wrapper(x, y):
        s_time = time.perf_counter()
        func(x, y)
        e_time = time.perf_counter()
        print(f"function{func.__name__}The running time is:{e_time-s_time}")
    return wrapper

def gogo(func):
    def wrapper(x, y):
        print("I'm the second decorator")
    return wrapper

@go
@gogo
def func(x, y):
    i = 0
    while i < 1000:
        i += 1
    print(f"x={x},y={y}")

if __name__ == '__main__':
    func(33, 55)

After decorating the decorator code, the call func(33,55) has been switched to go(gogo(func)), and gogo(func) code has been converted to the following:

def wrapper(x, y):
    print("I'm the second decorator")

When running go(wrapper), the code is converted to:

s_time = time.perf_counter()
print("I'm the second decorator")
e_time = time.perf_counter()
print(f"function{func.__name__}The running time is:{e_time-s_time}")

At this point, you will find that the parameters are lost during the running process.

functools.wraps

The use of decorator can greatly improve the reusability of code, but the disadvantage is that the meta information of the original function is lost, such as the meta information of the function__ doc__,__ name__:

# Decorator
def logged(func):
    def logging(*args, **kwargs):
        print(func.__name__)
        print(func.__doc__)
        func(*args, **kwargs)
    return logging

# function
@logged
def f(x):
    """Function documentation, description"""
    return x * x

print(f.__name__) # Output logging
print(f.__doc__) # Output None

The solution is very simple. Import from functools import wraps and modify the code as follows:

from functools import wraps
# Decorator
def logged(func):
    @wraps(func)
    def logging(*args, **kwargs):
        print(func.__name__)
        print(func.__doc__)
        func(*args, **kwargs)
    return logging

# function
@logged
def f(x):
    """Function documentation, description"""
    return x * x

print(f.__name__) # Output f
print(f.__doc__)  # Output function document, description

Class based decorator

In actual coding, "function decorators" are the most common, and "class decorators" appear much less frequently.

The basic usage of class based decorators is the same as that of function based decorators. Let's look at the following code first:

class H1(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        return '' + self.func(*args, **kwargs) + ''

@H1
def text(name):
    return f'text {name}'

s = text('class')
print(s)

Class H1 has two methods:

  • __ init__: Receive a function as a parameter, that is, the function to be decorated;
  • __ call__: Let class objects be called, similar to function calls. The trigger point is triggered when the decorated function is called.

Finally, it is well written in the appendix Blog , you can learn.

Here, the details of such ornaments will not be expanded until the practical operation of snowball related projects.

The decorator is a class and the decorator of a class is different in detail. The above mentioned decorator is a class. You can think about how to add a decorator to a class.

Built in decorator

Common built-in decorators are @ property, @ staticmethod, @ classmethod. This part is explained in the refinement object-oriented part, and this paper only makes simple remarks.

@property

If the method in the class is used as an attribute, it must have a return value, which is equivalent to getter. If @ func is not defined Setter modifier is a read-only property.

@staticmethod

Static methods do not need to represent self of their own objects and cls parameters of their own classes, just like using functions.

@classmethod

Class method does not need the self parameter, but the first parameter needs to be the cls parameter representing its own class.

Summary of this blog

There are a lot of articles about Python decorators on the Internet. It's not very difficult to learn. The real difficulty is to apply them properly in the project. I hope this blog can help you understand the decorators.
Other contents can also refer to the official manual

Keywords: Python Back-end

Added by SuperTini on Thu, 06 Jan 2022 02:42:54 +0200