I wrote it once before, but the meta-programming of the class is also the meta-class. In fact, the master is not thorough, although the decorator can basically complete the work of the meta-class. But understanding the meta-class can have a deeper understanding of learning with OOP.
Not much nonsense, started.
The first reference this time is that the book is the ninth chapter of "Python Study Notes" and is also a book of super gods.
The so-called metaprogramming treats the program as data, or completes the compile-time work at runtime.
When using decorators, it can be implemented without intruding into the interior, and even inserting extension logic without knowing it.
The essence of a decorator is a function, and a general decorator also returns a function. Closures are called in principle, because the parameters passed in are non-global variables.
def log (fn): # Internal decorator function def wrap (* args, ** kwargs): print (f "log: {args}, {kwargs}") # Indirectly call the original function return fn (* args, * * kwargs) # return wrap function instead of original function return wrap @log def add (x, y): return x + y if __name__ == '__main__': add (1, 2)
log: (1, 2), {}
@ Is Python's syntactic sugar. In fact, each @log executes xxx = log (xxx) xxx is the decorated function. So there is no problem with its application to multiple objective functions.
So any callable object can be used as a decorator
Compared with functions, using classes can create more complex decorators.
class Log2: def __init __ (self, func): # print ('__ int__') # Actually run this decorator, it is actually self = wraps (func) (self) wraps (func) (self) def __call __ (self, * args, ** kwargs): print (f "log: {args}, {kwargs}") # In fact, directly executing self can also call the decorated function. self () also executes func () by default, but directly executes self, Will call self's __call__ into an endless loop return self .__ wrapped __ (* args, ** kwargs) # Defines that __get__ changed objects will be treated as non-data type descriptors during class instantiation, read this attribute , it will first be called DEF __get __ (Self, instance, owner): # this may be called upon to determine whether the class inside # Print (instance, owner) iF iS instance None: return Self the else: # If you directly bind self to the instance, you can actually bind self .__ wrapped__ to the instance via types.MethodType return types.MethodType (self, instance)
class X:
@Log2
def test(self):
print('my name is test')
During the execution of the above class decoration, in the X instance process, the test function is turned into itself through wraps. When the subsequent instance calls the test method, the function is bound to the instance through __get__.
A class decorator I wrote above can decorate functions and methods. There is no rewrite descriptor protocol in this book. This way of writing is from the cookbook. It should be obvious that ordinary function decorators are more suitable for writing methods.
Because function objects implement the descriptor protocol and binding rules by default.
Nest multiple decorators
----> 1 @a 2 @b 3 def test():... In [4]: test = a(b(test))
The above demonstrates the actual situation when multiple decorators, the decorator structure is the return value of the previous decorator, the return value may be the packaging object, or the original function. In this way, you must pay attention to the order of arrangement, because the return value of each decorator is not the same.
We must ensure that the decorator of the type method is the outermost one, because it is impossible to determine how the inner decorator is implemented.
[5]: class X: ...: ...: @classmethod ...: @log ...: def test(cls):... ...:
Parameters, in addition to the decorated directory, can also pass other parameters to the decorator to achieve more customized features.
This is relatively easy to understand. I wo n’t copy books. Some books are defined as decorator factories.
When using the decorator, the parameter xxx is brought to the internal decorator when @log (xxx), and different logical functions have been implemented.
Attributes
We should make the wrapper function more like the original function, such as having some of the same attributes.
In [6]: import functools In [7]: def log(fn): ...: ...: @functools.wraps(fn) ...: def wrap(*args, **kwargs): ...: return fu(*args, **kwargs) ...: print(f'wrap:{id(wrap)}, func:{id(fn)}') ...: return wrap ...: In [8]: @log ...: def add(x: int, y: int) -> int: ...: return x + y ...: wrap:4372788576, func:4372788864 In [9]: add.__name__ Out[9]: 'add' In [10]: add.__annotations__ Out[10]: {'x': int, 'y': int, 'return': int} In [11]: id(add), id(add.__wrapped__) Out[11]: (4372788576, 4372788864) In [12]: def log(fn): ...: ...: ...: def wrap(*args,** kwargs): ...: return fu(*args, **kwargs) ...: print(f'wrap:{id(wrap)}, func:{id(fn)}') ...: wrap=functools.wraps(fn)(wrap) ...: return wrap ...: ...: In [13]: @log ...: def add(x: int, y: int) -> int: ...: return x + y ...: wrap:4405430896, func:4405430176 In [14]: id(add), id(add.__wrapped__) Out[14]: (4405430896, 4405430176) In [15]:
functools.wrap copies __module__, __name__, __doc__, __annotations__ and other attributes of the original function to the wrapper function, and also uses __wrapped__ to store the original function or the previous decorator return value.
You can open the interference of the decorator to the unit test accordingly. It is obvious from the code that functools.wraps is a decorator factory.
Type decorator
Decorators can also be used for types, the difference here is nothing more than that the received parameters are type objects
def log (cls): class wrapper: def __init __ (self, * args, ** kwargs): # Return the cls instance to the properties of the wrapper instance inst self .__ dict __ ['inst'] = cls (* args, ** kwargs ) def __getattr __ (self, item): value = getattr (self.inst, item) print (f'get: {item} = {value} ') return value def __setattr __ (self, key, value): print (f' set: {key} = {value} ') setattr (self.inst, key, value) # return class itself return wrapper @log class X: ... x = X () xa = 1 print (xa)
The above defines a class through the decorator, decorates a class, and decorates the instance of the decorated class into an attribute of the internal class instance of the decorator, to complete the function of being decorated.
The previous use of the packaging class is more troublesome, you can directly use the function, indirectly call the target construction method to create an instance.
Press ENTER to continue... In [16]: def log(cls): ...: functools.wraps(cls) ...: def wrap(*args,**kwargs): ...: o = cls(*args, **kwargs) ...: print(f'log: {o}') ...: return o ...: return wrap ...: In [17]: @log ...: class X:... In [18]: In [18]: X() log: <__main__.X object at 0x106a8f590> Out[18]: <__main__.X at 0x106a8f590> In [19]:
This directly returns the instantiated object.
application
Using the decorator function, we can write various auxiliary development tools to complete tasks such as call tracking, performance testing, and memory detection. Of course, more often used for model design, improve the code structure.
Call tracking
Record target call parameters, return value, and information such as the number of executions and execution time
In [19]: def call_count (fn): ...: ...: def counter (* args, ** kwargs): ...: counter .__ count__ + = 1 ...: return fn (* args, ** kwargs)
# Copy function attributes and keep state ...: counter .__ count__ = 0 ...: return counter ...: In [20]: @call_count ...: def a (): ... In [21]: @call_count ...: def b():... In [22]: In [22]: a();a();a.__count__ Out[22]: 2 In [23]: b();b();b();b.__count__ Out[23]: 3 In [24]: a();a.__count__ Out[24]: 3 In [25]:
View through closure
In [25]: a.__closure__ Out[25]: (<cell at 0x106f92510: function object at 0x107035950>, <cell at 0x106f92450: function object at 0x107035a70>) In [26]: a.__closure__[0].cell_contents Out[26]: <function __main__.call_count.<locals>.counter(*args, **kwargs)> In [27]: a.__closure__[1].cell_contents Out[27]: <function __main__.a()> In [28]:
There are two closure elements in a, one is the incoming function, and the other is the counter function, because the counter function has attribute assignment.
According to the definition of closure in this book , it means that after the function leaves the generation environment, it can still be remembered and continue to refer to external variables in the scope of the syntax.
There are two here, one is the external incoming function, and the other is the attribute of the defined function, both of which can still be remembered after the function leaves the generation environment.
There is a similar application in the standard library, which reduces the number of target executions by caching the results.
@functools.lru_cache
I rarely use this, just make a note for now
Property management
Add extra attributes to the target
In [43]: def pet(cls): ...: cls.dosomeing = lambda self:None ...: return cls ...: In [44]: @pet ...: class Parrot:... In [45]: In [45]: Parrot.__dict__ Out[45]: mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Parrot' objects>, '__weakref__': <attribute '__weakref__' of 'Parrot' objects>, '__doc__': None, 'dosomeing': <function __main__.pet.<locals>.<lambda>(self)>}) In [46]:
Instance management
A single case is written in the book
class Singleton: def __init__(self, cls): self.cls = cls self.inst = None def __call__(self, *args, **kwargs): if not self.inst: self.inst = self.cls(*args, **kwargs) return self.inst def singleton(cls): inst = None def wrap(*args, **kwargs): nonlocal inst if not inst: inst = cls(*args, **kwargs) return inst return wrap @Singleton class My: ... @singleton class My1: ... m1 = My1(); m2= My1() m3 = My(); m4 = My() print(m1 is m2, m3 is m4)
True True
Parts registration
class App: def __init __ (self): # Initialize the routing table self.routers = {} def route (self, url): # Correlate the address with the function through route, the outermost layer receives the parameters, and the layer receives the decorated function , Write to the routing table def register (fn): self.routers [url] = fn return fn return register app = App () @ app.route ('/') def index (): ... @ app.route ( '/ help') def help (): ... print (index) print (app.routers)
<function index at 0x107990290> {'/': <function index at 0x107990290>, '/help': <function help at 0x107990320>}
Descriptor
The function is a non-data descriptor, and the function can be changed because of the function of __get__.
Descriptor attributes must be defined as type members, so they are not suitable for storing instance-related state.
When creating an attribute, the __set_name__ method is called, and the target type (owner) and the attribute name can be known by parameters
Define a complete descriptor
class descriptor: # In the previous study, the initial value assignment was used through __init__, __set_name__first contact # The early fluent Python needs to pass __init__ and need to define different variable names to customize the unique self.name , very inconvenient # Here, it is very convenient to pass in the variable name when the attribute is copied from the beginning. The subsequent operations, it is very convenient to directly operate self.name # Checked this is Python3.6 or above Only, no wonder there is no smooth Python, use this convenient to create more descriptors def __set_name __ (self, owner, name): print (f'name: {owner .__ name __} ------- {name} ' ) # Copy instance by __set_name__ name self.name = f "__ {name} __" def __get __ (self, instance, owner): print (f "get: {instance}, {owner}") return getattr (instance , self.name , None) def __set __ (self, instance, value): print (f "set: {instance}, {value}") return setattr(instance, self.name, value) def __delete__(self, instance): print(f'del: {instance}') raise AttributeError("delete is disabled") class X: data = descriptor() data2 = descriptor() x = X() x.data=22 x.data2 = 55 print(x.data) print(x.data2)
/usr/local/bin/python3.7 /Users/shijianzhong/study/Python学习笔记/第九章元编程/9_2.py name: X-------data name: X-------data2 set: <__main__.X object at 0x10b99b990>, 22 set: <__main__.X object at 0x10b99b990>, 55 get: <__main__.X object at 0x10b99b990>, <class '__main__.X'> 22 get: <__main__.X object at 0x10b99b990>, <class '__main__.X'> 55 Process finished with exit code 0
Data descriptor
This pre-smooth Python is also introduced. The introduction here is more streamlined. As a memory, the previous ones are almost forgotten.
If the __set__ or __delete__ method is defined, we call it the data descriptor and the only __get__ is the non-data descriptor.
The difference between the two is that the priority of the data descriptor attribute is higher than the members of the same name in the instance namespace
class descriptor2: def __get __ (self, instance, owner): print ('__ get___') def __set __ (self, instance, value): print ('__ set__') class X2: data = descriptor2 () x = X2 () # and Descriptor attribute of the same name, if you want to pass in instance attributes, you can only pass in x .__ dict __ ['data'] = 200 # setattr (x, 'data', 200) print (vars (x)) print ( x.data)
{'data': 200} __get___ None
class descriptor2: def __get __ (self, instance, owner): print ('__ get___') # # def __set __ (self, instance, value): # print ('__ set__') class X2: data = descriptor2 () x = X2 ( ) # The attribute with the same name as the descriptor, if you want to pass in the instance attribute, you can only pass in x .__ dict __ ['data'] = 200 # setattr (x, 'data', 200) print (vars (x) ) print (x.data)
name: X-------data name: X-------data2 {'data': 200} 200
It can be seen that only the __get__ instance attribute can override the non-data descriptor
Property (property) is a data descriptor, because it defines __get__ and __set__ and __delete__
Method binding
Because the function implements the descriptor protocol by default, when accessing methods by instance or type, __get__ is called first
The type and instance are passed as parameters to __get__ to intercept the binding target (__self__), so the function wrapper or binding object is returned. Actually executed, this will hide the packaged product with the first parameter
It's very concise, so I have a fuller understanding of the function.
In [48]: class X: ...: def test(self, o): ...: print(o) ...: In [49]: x= X() In [50]: x.test Out[50]: <bound method X.test of <__main__.X object at 0x1072fa750>> In [51]: x.test.__get__(x,X) Out[51]: <bound method X.test of <__main__.X object at 0x1072fa750>> In [52]: m = x.test.__get__(x,X) In [53]: m.__self__, m.__func__ Out[53]: (<__main__.X at 0x1072fa750>, <function __main__.X.test(self, o)>)
What the book says is very vivid, we execute a method
For example x.test (123)
Can actually be divided into two steps
The first step is to wrap the function into a method and return m = x.test .__ get __ (x, X)
The second step uses the class to execute the function X.test (m .__ self__, 123)
In this case, the self inside is automatically filled in, because my understanding is wrong, I thought that I had to find the method again, in fact, I found the method through __get__, and passed in through __self__ in the method.
Yuan.
Metaclasses (metaclasses) create all types of objects and associate them with logical parent classes.