• python-__getattr__ 和 __getattribute__


    python3完全使用了新式类,废弃了旧式类,getattribute作为新式类的一个特性有非常奇妙的作用。查看一些博客和文章后,发现想要彻底理解getattr和getattribute的区别,实际上需要理解python中属性的查找顺序、描述器(descriptor)、__get__、__set__、__dict__等知识。

    首先需要指出,如果一个类只定义了__get__方法则称之为non-data descriptor(非资料描述器),如果这个类将__get__和 __set__都定义了,则称之为data descriptor(资料描述器),具体可见我的另一篇博客,方便理解。下面介绍实例属性的查找顺序。假设 t = T(),t.at的查找顺序如下:

    1、如果是python自动产生的属性,返回,否则进行第2步

    2、如果‘at’出现在了T或者其父类和祖先类__dict__中(即‘at’是一个类属性,并非只属于t的实例属性),并且at是一个data descriptor,则优先调用其__get__方法。不是data descriptor或者没有该属性则进行第2步。

    3、查找实例t的__dict__中是否有at属性,有则返回,没有则到第3步。

    4、查找t的父类和祖先类的__dict__中是否有at属性,如果没有则执行第4步,如果有则执行如下步骤:

    ​ 4.1 at是一个non-data descriptor,调用其__get__方法,不是则执行3.2

    ​ 4.2 返回__dict__['at']

    5、如果实例t的父类中有__getattr__方法,则调用该方法,没有则抛出AttributeError。

    注意每次类或实例调用属性时getattribute会被无条件首先调用。下方代码略长,耐心查看。

    class Descriptor:  # 定义描述器的类
        def __get__(self, instance, owner):  # get方法用于返回实例的a属性
            print('3 get called,', 'instance is', instance, ',owner is', owner)
            return instance.a
    
        def __set__(self, instance, value):  # set方法用于修改实例的a属性
            print('4 set called,', 'instance is', instance, ',value is', value)
            instance.a = value**2
    
        def __getattribute__(self, item): 
            print('5 Des getattribute called, item is %s' % item)
    
    class NotDescriptor: # 定义non-data descriptor
        def __get__(self, instance, owner):
            print('6 get called,', 'instance is', instance, ',owner is', owner)
            return instance.a - 100
    
    class T:
        desc = Descriptor()  # 类属性,一个资料描述器 data descriptor
        Not_Desc = NotDescriptor()  # 类属性,一个非资料描述器 non-data descriptor    
        def __init__(self):
            self.a = 123
            self.not_desc = 'instance not_desc'
            
        def func(self):
            return 'T func'
    
        def __getattribute__(self, item):
            print('1 getattribute called, item is ', item)
            return object.__getattribute__(self, item)
    
        def __getattr__(self, item):
            print('2 getattr called, item is', item)
            raise AttributeError('NO such attr %s' % item)
    

    以下的测试均使用上方的代码,每次的输出操作都重置过,以避免混淆。

    1 getattribute called, item is  func
    T func  # 实例的函数
    1 getattribute called, item is  a
    123  # 实例的属性
    ==================================================
    4 set called, instance is <__main__.T object at 0x000001E55B630240> ,value is 2
    # set方法前不调用getattribute。instance是拥有该描述器类的一个实例。value是要设置的值。
    ==================================================
    1 getattribute called, item is  desc
    3 get called, instance is <__main__.T object at 0x000001E55B630240> ,owner is <class '__main__.T'>
    # get方法前还是优先调用getattribute,instance是拥有该描述器对象的一个实例。owner是拥有者本身
    1 getattribute called, item is  a
    t.a = 4 # 2**2 = 4
    

    可见调用实例的属性和方法时都会无条件首先调用getattrtibute方法,而set方法则不会调用。另外代码利用描述器的get和set方法,实际上已经实现了类似于@property的使用(当然,property装饰器其实就是基于描述器实现的,对property装饰器不了解的话可以查看我的另一篇博客)。

    下面继续使用上方代码验证__getattr__、非描述器的执行顺序。

    t = T()
    print(t.not_desc)
    --------结果如下------
    1 getattribute called, item is not_desc
    instance not_desc
    

    回想前面提到的属性查找顺序,‘not_desc’在类的__dict__被找到了,但不是descriptor,所以执行第2步,在实例的__dict__中发现该属性,返回。

    t = T()
    print(t.Not_Desc)
    --------结果如下------
    1 getattribute called, item is  Not_Desc
    6 get called, instance is <__main__.T object at 0x0000017F70A006A0> ,owner is <class '__main__.T'>
    # 这里是NonDataDescriptor中的get方法
    1 getattribute called, item is  a
    23  # a的初始值为123 由get方法返回的是123-100=23
    

    根据我们之前提到的顺序,‘Not_Desc’属性在类的__dict__中找到但不是data descriptor,又没有在实例的dict中找到,所以进行到了4.1步骤,发现它是一个non-data descriptor,然后就执行了其中的get方法。

    t = T()
    print(t.bbbbbb)  # T类中没有定义该属性
    ------结果如下----------
    1 getattribute called, item is bbbbbb
    2 getattr called, item is bbbbbb
    Traceback (most recent call last):
      File "C:/省略/.py", line 40, in <module>
        print(t.bbbbbb)
      File "C:C:/省略/.py.py", line 36, in __getattr__
        raise AttributeError('NO such attr %s' % item)
    AttributeError: NO such attr bbbbbb
    

    可见,当我们访问一个没有被定义的属性时,仍然会首先调用getattribute,根据属性查找原则,在实例和类中都没有找到这个属性,于是执行getattr。到这里我们也就了解到了python中属性的查找顺序、getattribute和getattr的执行顺序。

    回过头来,getattribute会在调用类和实例的属性时无条件调用,所以可以用于权限鉴别、日志记录等操作;而getattr会在属性没有被找到的时候执行,因此可以用来做一些兜底的操作,可见这篇博客

    另外注意重写getattribute时的循环陷阱,返回语句要写成return object.__getattribute__(),不要使用return self.xxx,否则self相当于又指向了自己,而getattribute会无条件调用,从而进入无限循环。


    最后的我们查看类和实例的__dict__属性,看看又什么不同

    t = T()
    print(t.__dict__)
    print(T.__dict__)
    ------结果如下--------
    1 getattribute called, item is __dict__
    {'a': 123, 'not_desc': 'instance not_desc'}
    {'__module__': '__main__', 'desc': <__main__.Descriptor object at 0x0000021FD20C0128>, 'Not_Desc': <__main__.NotDescriptor object at 0x0000021FD20C0160>, '__init__': <function T.__init__ at 0x0000021FD20B59D8>, 'func': <function T.func at 0x0000021FD20B5A60>, '__getattribute__': <function T.__getattribute__ at 0x0000021FD20B5AE8>, '__getattr__': <function T.__getattr__ at 0x0000021FD20B5B70>, '__dict__': <attribute '__dict__' of 'T' objects>, '__weakref__': <attribute '__weakref__' of 'T' objects>, '__doc__': None}
    

    可见实例t只有init中定义的属性,而类T中才有描述器和非描述器等属性。

    参考:

    https://blog.csdn.net/yitiaodashu/article/details/78974596

    https://www.cnblogs.com/Vito2008/p/5280216.html

    https://www.cnblogs.com/pyxiaomangshe/p/7927540.html


    作者:bitterz
    本文版权归作者和博客园所有,欢迎转载,转载请标明出处。
    如果您觉得本篇博文对您有所收获,请点击右下角的 [推荐],谢谢!
  • 相关阅读:
    ossec配置使用腾讯企业邮箱告警
    网络排除工具之tcping
    pyenv 安装
    CVE-2020-1472 漏洞检测
    容器技术的核心
    简述 进程、线程、协程的区别 以及应用场景--记录
    php函数使用
    php使用表单post方法进行页面
    CURL方式使用代理访问网站
    nginx下隐藏admin和当前域名下得index.php
  • 原文地址:https://www.cnblogs.com/bitterz/p/10320256.html
Copyright © 2020-2023  润新知