回顾元编程,这次务必全部拿下(装饰器、元类)。

前面写过一次,但对于类的元编程也就是元类,其实掌握的并不彻底,虽然装饰器基本能完成元类的工作。但了解了元类可以对与OOP的学习有更深的认识。

废话不多,开始了。

首先这次参考的是书籍是《Python学习笔记》第九章,也是一个超级大神的书。

所谓的元编程将程序当做数据,或在运行期完成编译期的工作。

在利用装饰器(decorator),可在不侵入内部实现,甚至在不知道的情况下,插入扩展逻辑。

装饰器的本质是一个函数,一般普通的装饰器也返回一个函数,原理的使用中调用了闭包,因为传入的参数为非全局变量。

def log(fn):
    # 内部的装饰器函数
    def wrap(*args, **kwargs):
        print(f"log: {args},  {kwargs}")
        # 间接调用原函数
        return fn(*args, **kwargs)

    # 返回包装函数代替原函数
    return wrap


@log
def add(x, y):
    return x + y

if __name__ == '__main__':
    add(1, 2)
    
log: (1, 2),  {}

 @是Python的语法糖,其实每条@log执行的是xxx =log(xxx) xxx为被装饰的函数。所以其应用于多个目标函数没有问题。

因此任何可调用的对象都可以用来做装饰器

相比与函数,使用类可以创建更加复杂的装饰器。

class Log2:

    def __init__(self,func):
        # print('__int__')
        # 其实运行了这个装饰器,实际就是self = wraps(func)(self)
        wraps(func)(self)

    def __call__(self, *args, **kwargs):
        print(f"log: {args},  {kwargs}")
        # 其实直接执行self也可以调用被装饰的函数,self()默认也是执行func(),但直接执行self,会调用self的__call__陷入死循环
        return self.__wrapped__(*args, **kwargs)

    # 定义了__get__改对象在类实例化过程中会被当做非数据型描述符,对该属性读取时,会首先被调用
    def __get__(self, instance, owner):
        # 这个可以判断是否时在类里面被调用
        # print(instance, owner)
        if instance is None:
            return self
        else:
            # 如果是直接将self绑定给instance,其实也可以将self.__wrapped__通过types.MethodType绑定到instance上面
            return types.MethodType(self, instance)

class X:
@Log2
def test(self):
print('my name is test')

上面的 类装饰在执行过程中,在X实例过程中,将test函数通过wraps变成自身,后续实例调用test方式的时候,再通过__get__将该函数绑定到实例上面。

上面我写的一个类装饰器,可以装饰函数与方法,本书中没有重写描述符协议,这个写法是从cookbook中看来,应该这里来看明显普通的函数装饰器更加适合写方法。

因为函数对象默认实现了描述符协议和绑定的规则。

嵌套使用多个装饰器

----> 1 @a
      2 @b
      3 def test():...



In [4]: test = a(b(test))   

 上面演示了多个装饰器时候的实际情况,装饰器结构的是前一个装饰器的返回值,该返回值可能是包装对象,或是原函数。如此,就必须注意排列顺序,因为每个装饰器的返回值并不相同。

我们必须确保类型方法的装饰器是最外面的一个,因为无法确定内层装饰器如何实现。

 [5]: class X: 
   ...:      
   ...:     @classmethod 
   ...:     @log 
   ...:     def test(cls):... 
   ...:                        

参数,除被装饰的目录外,还可向装饰器传递其他参数,以实现更多定制特性。

这个相对比较好理解,我不抄书了,有些书上面定义为装饰器工厂

可以在用装饰器的时候,在@log(xxx)的时候给内部装饰器带去参数xxx,已实现不同的逻辑功能。

属性

我们应该让包装函数更像原函数一些,比如拥有某些相同的属性。

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将原函数的__module__,__name__,__doc__,__annotations__等属性复制到包装函数,还用__wrapped__存储原始函数或上一装饰器返回值。

可据此然开装饰器对单元测试的干扰,从代码中明显看到,functools.wraps是一个装饰器工厂。

类型装饰器

装饰器同样可用于类型,这里的区别无非是接收的参数为类型对象而已

def log(cls):

    class wrapper:

        def __init__(self, *args, **kwargs):
            # 将cls实例返回给wrapper实例的属性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 wrapper

@log
class X:
    ...

x = X()
x.a=1
print(x.a)

 上面通过装饰器内部定义一个类,装饰一个类,将被装饰的类的实例装饰成装饰器内部类实例的一个属性,完成被装饰的功能。

前面的使用包装类比较麻烦,可以直接使用函数,间接调用目标构建方法创建实例。

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]:                                    

 这个直接就返回了实例化的对象。

应用

利用装饰器功能,我们可以编写各种辅助开发工具,完成诸如调用跟踪、性能测试、内存检测等任务。当然更多的时候用于模型设计,改善代码结构。

调用跟踪

记录目标调用参数、返回值,以及执行次数和执行时间等信息

In [19]: def call_count(fn): 
    ...:      
    ...:     def counter(*args, **kwargs): 
    ...:         counter.__count__ += 1 
    ...:         return fn(*args, **kwargs)
        # 给函数属性复制,保持状态 ...: 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]:

 通过闭包的查看

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]:      

 a里面有两个闭包元素,一个传入的函数,还有一个就是counter函数,因为counter函数有了属性的赋值。

根据本书中闭包的定义,是指函数离开生成环境后,依然可记住,并持续引用语法作用域里的外部变量。

这里有两个,一个是外部传入函数,一个是定义的函数的属性,都在函数离开生成环境后,依然可记住。

在标准库中有类似的应用,通过缓存结果减少目标执行次数。

@functools.lru_cache

这个我用的很少,暂时就记一笔

属性管理

给目标添加额外属性

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]:    

 实例管理

书中写了一个单例

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

部件注册

class App:
    def __init__(self):
        # 初始化路由表
        self.routers = {}

    def route(self, url):
        # 通过route将地址与函数对应起来,最外层接收参数,该层接收被装饰的函数,写入路由表
        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>}

描述符

函数就是一个非数据描述符,函数能够变方法就是因为__get__的作用。

描述符属性必须定义为类型成员,所以其自身不适合存储实例相关的状态。

在创建属性时,__set_name__方法被调用,并可通过参数获知目标类型(owner),以及属性名称

定一个完整的描述符

class descriptor:

    # 前面的学习中都是通过__init__来使用初始值的赋值,__set_name__第一次接触
    # 前期流畅的Python需要通过__init__需要通过定义不同的变量名才可以,定制唯一的self.name,非常不方便
    # 这里通过__set_name可以非常方便的在一开始就传入属性复制的时候变量名,后续的操作,直接操作self.name非常方便
    # 查了一下这个是Python3.6以上才有的,难怪流畅的Python没有,用这个方便制作描述符多了
    def __set_name__(self, owner, name):
        print(f'name: {owner.__name__}-------{name}')
        # 通过__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

数据描述符

这个前期流畅的Python也有介绍,这里的介绍更加精简,当做回忆,前面的忘的差不多了

如果定义了__set__或__delete__方法,我们变称呼数据描述符(data descriptor)而仅有__get__的则是非数据描述符(non-data descriptor)。

这两者的区别在于,数据描述符属性的优先级高于实例名字空间中的同名成员

class descriptor2:

    def __get__(self, instance, owner):
        print('__get___')

    def __set__(self, instance, value):
        print('__set__')

class X2:
    data = descriptor2()

x = X2()
# 与描述符同名属性,如果想传入实例属性,只能通过__dict__传入
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()
# 与描述符同名属性,如果想传入实例属性,只能通过__dict__传入
x.__dict__['data'] = 200
# setattr(x, 'data' , 200)
print(vars(x))
print(x.data)
name: X-------data
name: X-------data2
{'data': 200}
200

 可以看到当仅有__get__实例属性能够覆盖非数据描述符

属性(property)是属于数据描述符,因为其定义了__get__与__set__还有__delete__

方法绑定

因为函数默认实现了描述符协议,所以当以实例或类型访问方法时,__get__首先被调用

类型和实例作为参数被传入__get__,从而截获绑定目标(__self__),如此就将函数包装或绑定对象返回。实际被执行的,就时这个会隐藏传入第一参数的包装品

说的很精简,让我对函数有了更加充分的认识。

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)>)

 书中说的很形象,在我们执行一个方法

比如x.test(123)

可以实际分为两步

第一步将函数包装成方法返回m = x.test.__get__(x, X)

第二步用类去执行函数X.test(m.__self__, 123)

这样的话,里面的self就自动填入,因为我的理解都时错的,以为是有了self再去找方法,其实是通过__get__找到方法,通过方法里面的__self__传入自身。

元类。

元类(metaclass)制造了所有的类型对象,并将其与逻辑上的父类关联起来。

猜你喜欢

转载自www.cnblogs.com/sidianok/p/12670915.html