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