Original text: https://markdowner.net/article/157600452270493696
1, Introduction
The old ape has always wanted to write a relatively complete blog on the introduction of decorators. It has been written for at least half a year since the beginning. It has not been finished yet, because there are still some knowledge points that have not been thoroughly studied, so it has been kept in the draft box. When writing this material, I found that many decorators in Python were introduced online, most of them were decorator functions, but very few introduced decorator classes or decorated classes with decorator functions. The old ape thinks that there should be four combinations of decorators and functions according to the decorator itself and the decorated object. As mentioned above, there should be another kind of combination in which both the decorator and the decorated object are classes. However, it is not found in the public data whether there can be a class decorator, that is, both the decorator and the decorated object are classes. The old ape has done a lot of tests on the function decorator and function decorator of the reference class. For a time, he thought he could not implement the class decorator of the class, and was ready to give up. After a long time, he recently spent two days on research and testing, and finally got through. Based on the above research, the old ape decided to write a separate detailed introduction to the four types of ornaments.
The concept of ornaments will not be introduced. According to the type of ornaments and the type of objects to be decorated, the old ape divides ornaments into the following four types:
- Function decorator of function: both decorator and decorated object are functions;
- Class function decorator: the decorator is a function and the decorated object is a class;
- Class decorator of function: the decorator is a class and the decorated object is a function;
- Class decorator of class: both the decorator and the decorated object are classes.
2, Function decorator for function
Decorators include decorative objects and decorated objects. The simplest decorator is to decorate the decorated function with decorator function. In this scenario, the decorator is a function decorator, and the decorated object is also a function.
2.1 overview
The function decorator is a special function. The parameter of the function is a function. Redefine a new function in the decorator function, use the decorated function before, after or in the middle of executing some functions, and finally return the newly defined function. Decorators can also be called function wrappers. In fact, they add some separate logic codes before or after the execution of the decorated function, so that the final result after the execution of the decorated function is affected by the logic of the decorated function, so as to change or limit the execution result of the decorated function.
2.2. Decorator definition syntax
@decoratorName def originalFunction(*args,**kwargvs): Function body
2.3 grammar explanation of decorator
- The definition of decorator is declared starting with the @ symbol
- decoratorName is the name of the decorator. There must be a closed function corresponding to decoratorName (see< Section 13.2 on closures >The closed function meets the following requirements:
- Parameter is a function object;
- There is a nested function inside the closed function, which will call the function specified by the closed function parameter and add additional other codes (these codes are decoration);
- The parameters of nested functions must contain the parameters of originalFunction, but cannot contain the decorated object originalFunction;
- The return value of the nested function must be similar to the return value of the function specified by the closed function parameter, and both meet the duck type requirements (for duck type, refer to< Section 7.3 object oriented design with Python features: protocols, polymorphisms, and types>);
- The return value of a closed function must be a nested function.
- For the definition of decorator function, refer to the following form:
def decoratorName(originalFunction,*args,**kwargvs): def closedFunction(*args,**kwargvs): ... #Some decorative code before the execution of the originalFunction function ret = originalFunction(*args,**kwargvs) ... #Some decorative code executed by the originalFunction function return ret return closedFunction
Where decoratorName is the decorator function, originalFunction is the decorated function, and closedFunction is the nested function within the decorator function.
-
The syntax defined by the decorator is essentially equivalent to the following statement:
originalFunction = decoratorName(originalFunction)
2.4 use of multi-layer decorators
Outside a function, you can define multiple decorators in sequence, such as:
@decorator1 @decorator2 @decorator3 def originalFunction(*args,**kwargvs): Function body
This kind of multiple decorators is actually superposition, and the upper decorator is the packaging of the lower decorator. The effect of the above definition statement is equivalent to the following statement:
originalFunction = decorator3(originalFunction) originalFunction = decorator2(originalFunction) originalFunction = decorator1(originalFunction)
That is, equivalent to:
originalFunction = decorator1(decorator2(decorator3(originalFunction)))
3, Function decorator for class
3.1 definition
Function decorator in addition to decorating the function (use the function name as the parameter of the decorator function) In addition, you can add a function decorator to a class. When adding a function decorator to a class, take the class name as the parameter of the decorator function, and define a class in the decorator function, such as wrapClass, which is called the wrapper class. The constructor of the wrapper class must call the decorated class to define an instance variable, and the decorator function will return the wrapper class, such as wrapClass.
3.2 function decorator case of class 1
def decorateFunction(fun, *a, **k): class wrapClass(): def __init__(self, *a, **k): self.wrappedClass=fun(*a, **k) def fun1(self,*a, **k): print("Prepare to call the method of the decorated class fun1") self.wrappedClass.fun1(*a, **k) print("Call the method of the decorated class fun1 complete") return wrapClass @decorateFunction class wrappedClass: def __init__(self ,*a, **k): print("I am the constructor of the decorated class") if a:print("The construction method has location parameters:",a) if k:print("Keyword parameters exist in the construction method:",k) print("Finished executing decorated class constructor") def fun1(self,*a, **k): print("I'm decorated fun1 method") if a:print("fun1 There are location parameters:",a) if k:print("fun1 Keyword parameters exist:",k) print("Decorated class fun1 Method execution completed") def fun2(self,*a, **k): print("I'm decorated fun2 method")
For the class wrappedClass decorated by the decorated function above, we execute the following statement:
>>> c1 = wrappedClass('testPara',a=1,b=2) I am the constructor of the decorated class The construction method has location parameters: ('testPara',) Keyword parameters exist in the construction method: {'a': 1, 'b': 2} Finished executing decorated class constructor >>> c1.fun1() Prepare to call the method of the decorated class fun1 I'm decorated fun1 method Decorated class fun1 Method execution completed Call the method of the decorated class fun1 complete >>> c1.fun2() Traceback (most recent call last): File "<pyshell#37>", line 1, in <module> c1.fun2() AttributeError: 'wrapClass' object has no attribute 'fun2' >>>
It can be seen that the related methods of decoration class must be invoked in the decoration class. After decoration, if the decoration function defines class, the same name function of the decorated class is not defined, and the class object returned after decorating can not execute the related method of the decorated class.
3.3 function decorator case of class 2
In the above case 1, the method of the decorated class is statically redefined in the decorated class inside the decorator function to support the method of the wrapped class. This situation can be used for the decorated class of the decorator, which only needs to call the specified known method. However, sometimes our decorator may be used to decorate multiple classes and only rewrite the construction method and specific method in the decorated class As a result, the functions to be called by the decorated class cannot be called. At this time, we need to implement a general method in the decorator to ensure that all the methods of the decorated class can be executed after the decorated class is decorated. This requires the dynamic definition of class instance methods with the help of setattr.
def decorateFunction(fun, *a, **k): class wrapClass(): def __init__(self, *a, **k): self.wrappedClass=fun(*a, **k) self.decorate() #Assign wrapClass as the instance variable to the method without override definition. In this case, the involved method is fun2 def fun1(self,*a, **k): print("Prepare to call the method of the decorated class fun1") self.wrappedClass.fun1(*a, **k) print("Call the method of the decorated class fun1 complete") def decorate(self):#Assign wrapClass as an instance variable to a method that has no override definition for m in dir(self.wrappedClass): if not m.startswith('_')and m!='fun1': fn = getattr(self.wrappedClass, m) if callable(fn): setattr(self, m,fn) return wrapClass @decorateFunction class wrappedClass: def __init__(self ,*a, **k): print("I am the constructor of the decorated class") self.name = a[0] if a:print("The construction method has location parameters:",a) if k:print("Keyword parameters exist in the construction method:",k) print("Finished executing decorated class constructor") def fun1(self,*a, **k): print("I'm decorated fun1 method") if a:print("fun1 There are location parameters:",a) if k:print("fun1 Keyword parameters exist:",k) print("My instance name is:",self.name) print("Decorated class fun1 Method execution completed") def fun2(self,*a, **k): print("I'm decorated fun2 method") if a:print("fun2 Method has positional parameters:",a) if k:print("fun2 Keyword parameters exist:",k) print("My instance name is:",self.name)
For the class wrappedClass decorated by the decorated function above, we execute the following statement:
>>> c1 = wrappedClass('c1',a=1,b=2) I am the constructor of the decorated class The construction method has location parameters: ('c1',) Keyword parameters exist in the construction method: {'a': 1, 'b': 2} Finished executing decorated class constructor >>> c2 = wrappedClass('c2',a=12,b=22) I am the constructor of the decorated class The construction method has location parameters: ('c2',) Keyword parameters exist in the construction method: {'a': 12, 'b': 22} Finished executing decorated class constructor >>> c1.fun1() Prepare to call the method of the decorated class fun1 I'm decorated fun1 method My instance name is: c1 Decorated class fun1 Method execution completed Call the method of the decorated class fun1 complete >>> c2.fun2() I'm decorated fun2 method My instance name is: c2 >>> c1.fun2() I'm decorated fun2 method My instance name is: c1 >>>
It can be seen that except that the fun1 method overridden in the decoration class can be executed normally, the method fun2 without rewriting can also be executed normally.
4, Class decorator for function
In addition to decorating functions or decorating classes with functions as decorators, you can also use classes as decorators for functions. When decorating a class as a function decorator, it is necessary to decorate the function as an instance member of the decorator class. After decorating, calling the related method actually invokes the instance object of the decoration class itself. To ensure that the instance object of the class can be invoked, it is necessary to add the class to the class. __ call__ method.
Case:
class decorateClass: def __init__(self,fun): self.fun=fun def __call__(self, *a, **k): print("Execute decorated function") return self.fun( *a, **k) @decorateClass def fun( *a, **k): print(f"I'm a function fun,With parameters:",a,k) print("Old ape Python Blog post Directory: https://blog.csdn.net/LaoYuanPython/article/details/109160152, please pay attention to the WeChat official account of the same name.)
After definition, relevant calls are executed as follows:
>>> f = fun('funcation1',a=1,b=2) Execute decorated function I'm a function fun,With parameters: ('funcation1',) {'a': 1, 'b': 2} Old ape Python Blog post Directory: https://blog.csdn.net/LaoYuanPython/article/details/109160152,Please pay attention to the official account of WeChat. >>>
5, Class decorator for class
The function decorator of a function, the function decorator of a class and the class decorator of a function were introduced respectively, but it was not found in the public data whether there can be a class decorator, that is, the decorator and the decorated object are classes. The old ape referred to the function decorator of a class and the class decorator of a function, and finally confirmed that the class decorator of a class can also be supported.
5.1 key points of realization
To implement the class decorator of a class, according to the research of the old ape, the implementation of the class decorator class needs to follow the following points:
- The decorator class must implement at least two instance methods, including _init _and _call _;
- The parameters of the decorator class construction method include self,wrapedClass,*a,**k, where wrapedClass represents the decorated class, a represents the location parameters of the decorated class construction method, and K represents the keyword parameters of the decorated class construction method. For location parameters and keyword parameters, please refer to< Chapter V function advanced section 5.1 detailed description of position parameters and keyword parameters of Python functions>;
- Define a wrapper class in the constructor of the decorator class, such as wrapClass. The wrapper class inherits from the parameter wrapedClass (i.e. decorated class) of the constructor of the decorator class. The constructor parameters of the wrapper class wrapClass are self,*a,**k, and the meanings of relevant parameters are the same as above;
- In the packaging class construction method, calling the construction method of the parent class, the incoming parameters a and k;
- In the construction method of decorator class, save wrapClass class with instance variable (such as self.wrapedClass);
- In the decorator class's __call__ method, call self.wrapedClass(*a,**k) to create an object of the decorated class and return the object.
The class decorator created according to the above steps can be used to decorate other classes. Of course, the above methods are only the conclusion of the old ape's own research and test, and the old ape is not sure whether there are other methods.
5.2. Cases of class decorators
class decorateClass: #Decorator class def __init__(self,wrapedClass,*a,**k): #wrapedClass represents the decorated class print("Ready to perform decoration class initialization") class wrapClass(wrapedClass): def __init__(self,*a,**k): print(f"Initialization starts with the encapsulated class instance. The location parameters include:{a}, Keyword parameter is{k}") super().__init__(*a,**k) print(f"End of initialization of encapsulated class instance") self.wrapedClass=wrapClass print("Decoration class initialization completed") def __call__(self, *a, **k): print("Initialization of decorated class object begins") wrapedClassObj = self.wrapedClass(*a,**k) print("End of initialization of decorated class object") return wrapedClassObj @decorateClass class car: def __init__(self,type,weight,cost): print("class car __init__ start...") self.type = type self.weight = weight self.cost = cost self.distance = 0 print("class car __init__ end.") def driver(self,distance): self.distance += distance print(f"{self.type}It has been driven cumulatively{self.distance}kilometre") print("Old ape Python Blog post Directory: https://blog.csdn.net/LaoYuanPython/article/details/109160152, please pay attention to the WeChat official account of the same name.) c = car('Elysee','1.2 ton',8) c.driver(10) c.driver(110)
Execute the above code and the output is as follows:
Ready to perform decoration class initialization Decoration class initialization completed Initialization of decorated class object begins Initialization starts with the encapsulated class instance. The location parameters include:('Elysee', '1.2 ton', 8), Keyword parameter is{} class car __init__ start... class car __init__ end. End of initialization of encapsulated class instance End of initialization of decorated class object Elysee has been driving10kilometre Elysee has been driving120kilometre Old ape Python Blog post Directory: https://blog.csdn.net/LaoYuanPython/article/details/109160152,Please pay attention to the official account of WeChat.
In addition to the above methods, the old ape has found a simpler method. For details, please refer to< Implementation ideas and cases of class decorator>.
6, Summary
This paper introduces four types of decorators in Python in detail. According to the types of decorators and decorated objects, these four types of decorators are divided into function decorators of functions, function decorators of classes, class decorators of functions and class decorators of classes. This paper introduces the implementation steps of the four types of decorators in detail, and provides corresponding implementation cases. The relevant introduction is helpful for you to comprehensively and in detail Understand Python decorators.
https://markdowner.net/article/157600452270493696