在Python中有这两个魔法方法容易让人混淆:__getattr__
和__getattribute__
。
这两个方法都是用来获取对象的属性。
但是通常我们会定义__getattr__
,而从来不会定义__getattribute__
。
关于这两个方法,有如下几点需要说明。
(1)一般在获取对象的属性(包含通过对象获取类属性)时,就会走__getattribute__
方法,其中使用点的方式,还有__dict__
、getattr
和hasattr
都会走。
看下面一个例子:
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
- 首先会在对象的实例属性中寻找,找不到执行下一步
- 来到对象所在的类中查找类属性,如果还找不到执行下一步
- 来到对象的继承链上寻找,如果还找不到执行下一步
- 调用
obj.__getattr__
方法,如果用户没有定义或者还是找不到,抛出AttributeError异常,属性查找失败!
(7)为什么有了__getattribute__
还要__getattr__
?
根据上面的了解,既然获取属性时一定会走__getattribute__
,那当属性不存在时,自己在__getattribute__
处理不可以吗?为什么还要单独定义一个函数__getattr__
?
我的理解是为了让程序员自己定义获取不到属性时要做哪些操作。与__getattribute__
方法分开,方便获取属性时,不影响正常获取属性的方法前提下,程序员可以自定义获取不到属性时做哪些操作。所以一般我们只会重写__getattr__
方法,而不会动__getattribute__
方法。
当然这只是我的个人理解,有大佬其他理解欢迎讨论。
总结:
- 在获取对象的属性(包含通过对象获取类属性)时,不论属性存不存在,都会走
__getattribute__
方法; - 当访问的属性不存在的时候,就会调用
__getattr__
方方法; - 当
__getattr__
和__getattribute__
两个方法都存在时,__getattr__
方法不会再被调用,除非显示调用__getattr__
方法或引发AttributeError异常; - 当父类有
__getattribute__
方法,子类没有时,当子类用.形式查找属性时也会调用父类的该方法; - 当在
__getattribute__
代码块中,再次执行属性的获取操作时,会再次触发__getattribute__
方法的调用,代码将会陷入无限递归。可在里面使用object来执行获取属性操作。