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.

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__} run 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) {fun.__name__} {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): # def wrapper(): S_time = time.perf_counter() func() e_time = time.perf_counter() print(f) {func.__name__} {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.

Decorates 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" {func.__name__} {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(' execute ') def my_fun(x): Print (f" my_fun, my parameter {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(' execute ')(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.

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" {func.__name__} {e_time-s_time}") return wrapper def gogo(func): def wrapper(x, y): 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() print() return wrapper1 def d2(func): import time def d1(func): def wrapper1(): print() Def wrapper2(): print(" print ") return wrapper2@d1@d2 def func(): print(" print ") 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() def wrapper1(): Def d2(): def d2() def d2(): def d2(): def d2(): def d2(): def d2(): def d2(): 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.

Def wrapper2(): Print () print() print() print() print() print() print() print( Wrapper1 (): print(" decorator 1 starts decorator ") print(" decorator 2 starts decorator ") print(" decorator 2 ends decorator ") print(" decorator 1 ends decorator ")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" {func.__name__} {e_time-s_time}") return wrapper def gogo(func): def wrapper(x, y): 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 am the second wrapper ")Copy the code

In running go(Wrapper), the code is converted to:

S_time = time.perf_counter() print(" I am the second decorator ") e_time = time.perf_counter() print(f" {func.__name__}) {e_time-s_time}")Copy the code

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

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:

Def logged(*args, **kwargs): Print (func.__name__) print(func.__doc__) func(*args, **kwargs) return logging # @logged def f(x): Return x * x print(f.__name__) # Output logging print(F. __doc__) # Output NoneCopy the code

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

Def logged(func): @wraps(func) def logging(*args, **kwargs): Print (func.__name__) print(func.__doc__) func(*args, **kwargs) return logging # @logged def f(x): Return x * x print(f.__name__) # print(f.__doc__) # print(fCopy the code

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 '' + self.func(*args, **kwargs) + ''

@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.

Built-in decorator

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.

@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.

@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.

@classmethod

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

Summary of this blog post

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