Looking back at metaprogramming, be sure to get all this time (decorators, metaclasses).

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.

 

Guess you like

Origin www.cnblogs.com/sidianok/p/12670915.html