Eraser, a new series of fun Internet advanced webworms, let’s Be More Pythonic together.

Function decorator

In Python, Decorators are used to modify functions without changing the original function code. Decorators return a function object, so they are sometimes called “functions of functions.” There is also a design pattern called the decorator pattern, which will be covered in this course.

For decorator calls, use @, which is a Python programming syntax candy that will make your code look more Pythonic.

7.1 Basic use of decorators

One of the most common examples of decorators is counting the running time of a function, which I’ll share with you. Calculate the running time of the function:

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}")
Copy the code

If you want to add call times to each correspondence class, that’s a lot of work, and you have to change the code inside the function repeatedly, or change the code where the function is called. From this need, the decorator syntax emerged.

Let’s take a look at the first modification, which does not add decorators but writes a generic function that takes advantage of Python’s ability to use functions as arguments to achieve code reusability.

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)
Copy the code

This technique is then extended to Python’s decorator syntax with the following modifications:

import time

def go(func) :
	The wrapper function name can be any
    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()
Copy the code

In the above code, notice the go function, whose argument func is a function, and whose return value is an internal function. After executing the code, it is equivalent to injecting the calculation time into the original function. In the code call section, you don’t make any changes, and the func function has more functionality (the ability to calculate runtime).

The decorator function successfully extends the functionality of the original function without modifying the original function code. After learning this example, you will have a preliminary understanding of decorators.

7.2 Decorate functions with arguments

Look directly at the code to see how to decorate a function with arguments:

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)
Copy the code

If you’re getting confused, LET me give you a little bit of an emphasis on the passing of parameters.

There is also a case where the decorator itself takes parameters, such as the following code:

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

@log('execution')
def my_fun(x) :
    print(F "I'm my_fun, my argument{x}")

my_fun(123)
Copy the code

The code above nested a layer of functions on the outside of the decorator function. The final code runs in the following order:

my_fun = log('execution')(my_fun)
Copy the code

If we summarize at this point, we can conclude that using a decorator with arguments is wrapping a function around the decorator that receives arguments and returns a decorator function. It is also important to note that the decorator can only accept one argument, and it must be of function type.

7.3 Multiple Decorators

First copy the following code, 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)
Copy the code

After the code runs, the output is:

I am the second decorator function wrapper running time is: 0.0034401339999999975Copy the code

Print (f”x={x},y={y}”); print(f”x={x},y={y}”);

First, explain the order in which decorators are decorated.

import time
def d1(func) :
    def wrapper1() :
        print("Decorator 1 begins to decorate")
        func()
        print("Decorator 1 finishes decoration")
    return wrapper1

def d2(func) :
    def wrapper2() :
        print("Decorator 2 begins to decorate")
        func()
        print("Decorator 2 finishes decoration")
    return wrapper2

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

if __name__ == '__main__':
    func()
Copy the code

The above code runs as follows:

Decorator 1 Start Decorator 2 Start decorator decorated function decorator 2 End Decorator 1 End decoratorCopy the code

You can see the very symmetrical output and prove that the decorated function is in the innermost layer. The code converted to the function call looks like this:

d1(d2(func))
Copy the code

What you need to note in this section is that the statements between the outer and inner functions of the decorator are not decorated to the target function, but are appended to the decorator when it is loaded. When you decorate a function, the code between the outer and inner functions is run.

The test results are as follows:

import time

def d1(func) :
    print("I'm the code between the inside and outside functions of D1")
    def wrapper1() :
        print("Decorator 1 begins to decorate")
        func()
        print("Decorator 1 finishes decoration")
    return wrapper1

def d2(func) :
    print("I'm the code between the inside and outside functions of D2.")
    def wrapper2() :
        print("Decorator 2 begins to decorate")
        func()
        print("Decorator 2 finishes decoration")
    return wrapper2

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

After you run it, you should see the following output:

I'm the code between the inside and outside functions of D2 and the outside functions of D1Copy the code

The d2 function runs before the d1 function.

Let’s review the concept of decorators: the name of the decorated function is passed as an argument to the decorator function. After executing its own internal code, the decorator function assigns its return value to the decorator function.

D1 (d2(func)), d2(func), func refers to wrapper2, d1(wrapper2), The function name wrapper2 in turn points to wrapper1. So by the time the final func is called, the code has already switched to something like this.

The first step #
def wrapper2() :
     print("Decorator 2 begins to decorate")
     print("Decorated function")
     print("Decorator 2 finishes decoration")

Step # 2
print("Decorator 1 begins to decorate")
wrapper2()
print("Decorator 1 finishes decoration")

Step # 3
def wrapper1() :
	print("Decorator 1 begins to decorate")
	print("Decorator 2 begins to decorate")
    print("Decorated function")
    print("Decorator 2 finishes decoration")
	print("Decorator 1 finishes decoration")
Copy the code

The code after step 3 above runs exactly the same as our code output.

Now let’s go back to the case at the beginning of this section, why the output data was 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)
Copy the code

After performing the decorator code decoration, the call to func(33,55) has been switched to go(gogo(func)), and running the gogo(func) code converts to the following:

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

In 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}")
Copy the code

At this point, you will notice that the parameters are dropped during the run.

7.4 functools. Wraps

Using decorators can greatly improve code reuse, but the disadvantage is that the meta information of the original function is lost, such as __doc__, __name__ of the function:

# a 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
Copy the code

The solution is very simple, import from functools import Wraps and modify the code as follows:

from functools import wraps
# a 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__) F # output
print(f.__doc__)  Output function documentation, description
Copy the code

7.5 Class-based decorators

In actual coding, “function decorators” are the most common, and “class decorators” are much less common.

Class-based decorators are consistent with the basic function-based usage, starting with the following code:

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

    def __call__(self, *args, **kwargs) :
        return '<h1>' + self.func(*args, **kwargs) + '</h1>'

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

s = text('class')
print(s)
Copy the code

Class H1 has two methods:

  • __init__: Accepts a function as an argument, that is, the function to be decorated;
  • __call__: makes class objects callable, similar to function calls, with trigger points that are triggered when decorated functions are called.

Finally in the appendix a good blog to write, you can go to learn.

The details of the class decorator will not be developed here until the snowball related project is implemented later.

Decorators for classes are different in detail from class decorators. As mentioned above, decorators are classes. You can think about how to add decorators to classes.

7.6 Built-in decorators

Common built-in decorators are @Property, @StaticMethod, and @classMethod. This part of the content in the elaboration of the object – oriented part of the explanation, this article only do simple remarks.

7.6.1 @ property

To use an in-class method as a property, it must have a return value, which is equivalent to a getter, and is read-only if the @func.setter modifier is not defined.

7.6.2 @ staticmethod

Static methods that don’t need to represent their own object’s self and CLS arguments of their own class, just like using a function.

7.6.3 @ classmethod

Class method. No self argument is required, but the first argument needs to be a CLS argument representing its own class.

7.7 Summary of this blog

Python decorators there are a lot of articles on the web about Python decorators. It’s not that hard to learn, but to use them properly in a project. I hope this blog has helped you understand decorators. Other content can also refer to the official manual.

Blogger ID: Dream eraser, hope you like, comment, favorites.