zoukankan      html  css  js  c++  java
  • python中__getattr__和__getattribute__区别

      重载__getattr__方法对类及其实例未定义的属性有效。如果访问的属性存在,就不会调用__getattr__方法。这个属性的存在,包括类属性和实例属性

    class ClassA:
    
        x = 'a'
    
        def __init__(self):
            self.y = 'b'
    
        def __getattr__(self, item):
            return '__getattr__'
    
    if __name__ == '__main__':
        a = ClassA()
      
        print(a.x)# 输出结果 a
        # 使用实例直接访问实例存在的属性时,不会调用__getattr__方法
      
        print(a.y) # 输出结果 b
        # 使用实例直接访问实例不存在的属性时,会调用__getattr__方法
       
        print(a.z) # 输出结果 __getattr__

      __getattribute__仅在新式类中可用,重载__getattrbute__方法对类实例的每个属性访问都有效,无论属性存不存在都会先调用__getattribute__方法

    class ClassA:
    
        x = 'a'
    
        def __init__(self):
            self.y = 'b'
    
        def __getattribute__(self, item):
            return '__getattribute__'
    
    
    if __name__ == '__main__':
        a = ClassA()
    
        # 使用实例直接访问存在的类属性时,会调用__getattribute__方法
        print(a.x)     # 输出结果 __getattribute__
    
        # 使用实例直接访问实例存在的实例属性时,会调用__getattribute__方法
          print(a.y) # 输出结果 __getattribute__
    
        # 使用实例直接访问实例不存在的实例属性时,也会调用__getattribute__方法
        print(a.z) # 输出结果 __getattribute__

    当同时定义__getattribute__和__getattr__时,__getattr__方法不会再被调用,除非显示调用__getattr__方法或引发AttributeError异常。

    class ClassA:
    
        def __getattr__(self, item):
            print('__getattr__') 
    
        def __getattribute__(self, item):
            print('__getatttribute__')
    
    if __name__ == '__main__':
        a = ClassA()
        a.x
    
    运行结果
    __getatttribute__

    由于__getattr__只针对未定义属性的调用,所以它可以在自己的代码中自由地获取其他属性,

    而__getattribute__针对所有的属性运行,因此要十分注意避免在访问其他属性时,再次调用自身的递归循环。死循环!!

    当在__getattribute__代码块中,再次执行属性的获取操作时,会再次触发__getattribute__方法的调用,代码将会陷入无限递归,直到Python递归深度限制(重载__setter__  __setattr__方法也会有这个问题)。

    示例代码(无限递归):

    class ClassA:
    
        x = 'a'
    
        def __getattribute__(self, item):
            print('__getattribute__')
            return self.item    #再次出现属性的获取操作,会再次触发__getattribute__的调用
                     #相当于return self.__getattribute__(item) 
    
    if __name__ == '__main__':
        a = ClassA()
        a.x
    
    运行结果,达到最大递归深度
    ecursionError: maximum recursion depth exceeded

    也没办法通过从__dict__取值的方式来避免无限递归(重写__setattr__可以通过__dict__取值的方式来避免无限递归)

    class ClassA:
    
        x = 'a'
    
        def __getattribute__(self, name):
            return self.__dict__[name]  #__dict__魔法方法可以查看对象的属性,返回一个字典,键代表属性名  ,这样再次出现属性获取的操作,会再次触发__getattribute__
    
    if __name__ == '__main__':
        a = ClassA()
        
        a.x    # 无限递归

    为了避免无限递归,应该把获取属性的方法 __getattribute__指向一个更高的超类,例如object(因为__getattribute__只在新式类中可用,而新式类所有的类都显式或隐式地继承自object,所以对于新式类来说,object是所有新式类的超类)。利用super()方法

    class ClassA:
    
        x = 'a'    #类属性
    
        def __getattribute__(self, item):
            print('__getattribute__')
            return super().__getattribute__(self, item)
    
    
    if __name__ == '__main__':
        a = ClassA()
        print(a.x)        #输出__getattribute__  
                    a

    调用__getattr__详细过程如下:
     obj.attribute

    1. 首先会在对象的实例属性中寻找,找不到执行第二步
    2. 来到对象所在的类中查找类属性,如果还找不到执行第三步
    3. 来到对象的继承链上寻找,如果还找不到执行第四步
    4. 调用obj.__getattr__方法,如果用户没有定义或者还是找不到,抛出AttributeError异常,属性查找失败
    class MyClass:
    
        def __init__(self, x):
            self.x = x
    >>> obj = MyClass(1)
    >>> obj.y
    
    AttributeError: 'MyClass' object has no attribute 'a'

    如上代码,没有定义__getattr__魔法方法,又找不到属性,就会抛出异常

    调用__getattrIbute__方法

    当我们调用对象的属性时,首先会调用__getattribute__魔法方法。无论对象存不存在;当__getattribute__查找失败,就会去调用__getattr__方法。

    obj.x
    obj.__getattribute__(x)
    这两个代码其实是等价的

    使用__getattribute__魔法方法时,要返回父类的方法,(super函数)不然很难写对!!会导致无限递归!

    另外,内置的bif   getattr和hasattr也会触发这个魔法方法__getattribute__!!

     

    其他细节需要注意

    1.  _getattribute__的查找顺序

    class MyClass:
    
        x = 999              #类属性x
        
        def __init__(self, x):    #形参x
            self.x = x         #实例属性x
    
        def __getattribute__(self, item):
    
            print('正在获取属性{}'.format(item))
            return super(MyClass, self).__getattribute__(item)

    >>> obj = MyClass(2)
    >>> print(obj.x)
    正在获取属性x
    2
    >>> del obj.x     #删除了实例属性x

    >>> print(obj.x)   #此时访问的是类属性
    正在获取属性
    999

    上面代码中,定义了一个类属性x和一个实例属性x,这两个属性同名根据Python语法规则,当对象获取属性x的时候,首先会在实例属性中寻找,如果找不到才回去类属性中查找

    这便是__getattribute__的查找顺序。通常该方法在框架中可能会用到,一般情况下无需使用

    2.  super 对象没有 __getattr__魔法方法!!

    >>> class C:
            def __getattr__(self, name):
                    print(1)
                    return super().__getattr__(name)
            def __getattribute__(self, name):
                    print(2)
                    return super().__getattribute__(name)
            def __setattr__(self, name, value):
                    print(3)
                    super().__setattr__(name, value)
            def __delattr__(self, name):
                    print(4)
                    super().__delattr__(name)
    
                    
    >>> c = C()
    >>> c.x
    
    运行结果:
    
    >>> c = C()
    >>> c.x
    2
    1
    Traceback (most recent call last):
      File "<pyshell#31>", line 1, in <module>
        c.x
      File "<pyshell#29>", line 4, in __getattr__
        return super().__getattr__(name)
    AttributeError: 'super' object has no attribute '__getattr__'

    分析一下:首先 c.x 会先调用 __getattribute__() 魔法方法,打印 2;然后调用 super().__getattribute__(),找不到属性名 x,因此会紧接着调用 __getattr__() ,于是打印 1;你希望最后以 super().__getattr__() 终了的时候,Python 竟然告诉你 AttributeError,super 对象木有 __getattr__ !

    证明:

    >>> dir(super)
    ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__',
    '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
    '__self__', '__self_class__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__thisclass__']

    dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息)

    3.  初学常犯错误没有“观前顾后”!

    例如:编写一个 Counter 类,用于实时检测对象有多少个属性。

    class Counter:
            def __init__(self):
                    self.counter = 0       # 属性赋值,这里会触发 __setattr__ 调用
            def __setattr__(self, name, value):  #self是绑定的对象,name是属性名(name必须是字符串),value是为对象属性赋的值
                    self.counter += 1
                    super().__setattr__(name, value) #这时候 self.counter 还没有定义,所以没法 += 1,错误的根源。
            def __delattr__(self, name):
                    self.counter -= 1
                    super().__delattr__(name)

    >>>c =Counter()

    运行结果:
    AttributeError: 'Counter' object has no attribute 'counter'

    正确代码:

     class Counter:
            def __init__(self):
                    super().__setattr__('counter', 0)            #调用基类的赋值魔法方法__setattr__(name,value)  name必须是字符串!
            def __setattr__(self, name, value):
                    super().__setattr__('counter', self.counter + 1)
                    super().__setattr__(name, value)
            def __delattr__(self, name):
                    super().__setattr__('counter', self.counter - 1)
                    super().__delattr__(name)

    另外的

    __setattr__(self, name, value)

    定义当一个属性被设置时的行为

    __delattr__(self, name)

    定义当一个属性被删除时的行为

  • 相关阅读:
    Integer to English Words
    Word Ladder II
    Word Ladder
    Distinct Subsequences
    Interleaving String
    Scramble String
    【转】ROC和AUC介绍以及如何计算AUC
    Minimum Size Subarray Sum
    Minimum Window Substring
    Edit Distance
  • 原文地址:https://www.cnblogs.com/hemengjita/p/12288429.html
Copyright © 2011-2022 走看看