在Python中有这两个魔法方法容易让人混淆:__getattr____getattribute__

这两个方法都是用来获取对象的属性。

但是通常我们会定义__getattr__,而从来不会定义__getattribute__

关于这两个方法,有如下几点需要说明。

(1)一般在获取对象的属性(包含通过对象获取类属性)时,就会走__getattribute__方法,其中使用点的方式,还有__dict__getattrhasattr都会走。

看下面一个例子:

class MyClass(object):

    c = "c"

    def __init__(self, a):
        self.a = a

    def __getattribute__(self, item):
        print("---getattribute方法执行")
        return "getattribute"


if __name__ == "__main__":
    obj = MyClass("a")
    # 1.使用点获取属性会调用getattribute方法
    print(obj.a)
    # 2.使用getattr获取属性也会调用getattribute方法
    getattr(obj, "a")
    # 3.使用hasattr获取属性也会调用getattribute方法
    hasattr(obj, "a")
    # 4.通过对象获取类属性,也调用getattribute方法
    print(obj.c)

(2)如果访问属性存在(包含对象属性和类属性),就不会调用__getattr__方法。当访问的属性不存在的时候,就会调用该方法。

看下面一个例子:

class MyClass(object):

    c = "c"

    def __init__(self, a):
        self.a = a

    def __getattr__(self, item):
        print("---getattr方法执行")
        return "getattr"


if __name__ == "__main__":
    obj = MyClass("a")

    # 1.获取到对象属性不会调用getattr方法
    print(obj.a)
    # 2.获取不到对象属性不会调用getattr方法
    print(obj.b)

打印结果如下:

a
---getattr方法执行
getattr

那么如果不实现__getattr__方法,把方法注释掉,找不到属性就会报错:

AttributeError: 'MyClass' object has no attribute 'b'

(3)当__getattr____getattribute__两个方法都存在时,不管访问属性存不存在都只走__getattribute__方法,__getattr__方法不会再被调用,除非显示调用__getattr__方法或引发AttributeError异常。

看下面一个例子:

class MyClass(object):

    c = "c"

    def __init__(self, a):
        self.a = a

    def __getattribute__(self, item):
        print("---getattribute方法执行")
        return "getattribute"

    def __getattr__(self, item):
        print("---getattr方法执行")
        return "getattr"


if __name__ == "__main__":
    obj = MyClass("a")

    # 1.属性存在则只走__getattribute__方法
    print(obj.a)
    # 2.属性不存在,只走__getattribute__方法
    print(obj.b)

打印结果如下:

---getattribute方法执行
getattribute
---getattribute方法执行
getattribute

(4)当父类有__getattribute__方法,子类没有时,当子类用.形式查找属性时也会调用父类的该方法。

看下面的示例:

class A:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __getattribute__(self, item):
        return "A getattribute"


class B(A):
    def __init__(self, name, age):
        self.name = name
        self.age = age


if __name__ == "__main__":
    obj = B("Tom", 21)
    print(obj.sex)
    print(obj.name)

打印如下:

A getattribute
A getattribute

(5)当在__getattribute__代码块中,再次执行属性的获取操作时,会再次触发__getattribute__方法的调用,代码将会陷入无限递归。

看下面的例子:

class MyClass(object):

    c = "c"

    def __init__(self, a):
        self.a = a

    def __getattribute__(self, item):
        print("---getattribute方法执行")
        return self.item


if __name__ == "__main__":
    obj = MyClass("a")

    print(obj.a)

会发生如下错误:

RecursionError: maximum recursion depth exceeded while calling a Python object

也没办法通过从__dict__取值的方式来避免无限递归:

class MyClass(object):

    c = "c"

    def __init__(self, a):
        self.a = a

    def __getattribute__(self, item):
        print("---getattribute方法执行")
        return self.__dict__[item]


if __name__ == "__main__":
    obj = MyClass("a")

    print(obj.a)

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

class MyClass(object):

    c = "c"

    def __init__(self, a):
        self.a = a

    def __getattribute__(self, item):
        print("---getattribute方法执行")
        return super(MyClass, self).__getattribute__(item)


if __name__ == "__main__":
    obj = MyClass("a")

    print(obj.a)

打印结果如下:

---getattribute方法执行
a

(6)补充:获取属性时的寻找过程

例如使用obj.attr

  1. 首先会在对象的实例属性中寻找,找不到执行下一步
  2. 来到对象所在的类中查找类属性,如果还找不到执行下一步
  3. 来到对象的继承链上寻找,如果还找不到执行下一步
  4. 调用obj.__getattr__方法,如果用户没有定义或者还是找不到,抛出AttributeError异常,属性查找失败!

(7)为什么有了__getattribute__还要__getattr__

根据上面的了解,既然获取属性时一定会走__getattribute__,那当属性不存在时,自己在__getattribute__处理不可以吗?为什么还要单独定义一个函数__getattr__

我的理解是为了让程序员自己定义获取不到属性时要做哪些操作。与__getattribute__方法分开,方便获取属性时,不影响正常获取属性的方法前提下,程序员可以自定义获取不到属性时做哪些操作。所以一般我们只会重写__getattr__方法,而不会动__getattribute__方法。

当然这只是我的个人理解,有大佬其他理解欢迎讨论。

总结:

  1. 在获取对象的属性(包含通过对象获取类属性)时,不论属性存不存在,都会走__getattribute__方法;
  2. 当访问的属性不存在的时候,就会调用__getattr__方方法;
  3. __getattr____getattribute__两个方法都存在时,__getattr__方法不会再被调用,除非显示调用__getattr__方法或引发AttributeError异常;
  4. 当父类有__getattribute__方法,子类没有时,当子类用.形式查找属性时也会调用父类的该方法;
  5. 当在__getattribute__代码块中,再次执行属性的获取操作时,会再次触发__getattribute__方法的调用,代码将会陷入无限递归。可在里面使用object来执行获取属性操作。