__getattr__() 和 __getattribute__() 方法的区别

python 在访问属性的方法上定义了__getattr__()__getattribute__() 2种方法,其区别非常细微,但非常重要。

  • 如果某个类定义了 __getattribute__() 方法,在 每次引用属性或方法名称时 Python 都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)。
  • 如果某个类定义了 __getattr__() 方法,Python 将只在正常的位置查询属性时才会调用它。如果实例 x 定义了属性 colorx.color 将 不会 调用x.__getattr__('color');而只会返回 x.color 已定义好的值。

下边举几个栗子:

当一个类没有定义__getattr____getattribute__时,在访问类的实例一个不存在的属性时会报错

1
2
3
4
5
6
7
8
class GetAttrClass(object):
def __init__(self):
pass
if __name__ == '__main__':
gac = GetAttrClass()
print gac.x

上边程序运行后得到一下错误:

1
2
3
4
Traceback (most recent call last):
File "/Users/pan/PycharmProjects/test/getattr.py", line 13, in <module>
print gac.x
AttributeError: 'GetAttrClass' object has no attribute 'x'

当一个类定义__getattr____getattribute__时,在访问类的实例一个不存在的属性时会返回None

1
2
3
4
5
6
7
8
9
10
11
class GetAttrClass(object):
def __init__(self):
pass
def __getattr__(self, item):
pass
if __name__ == '__main__':
gac = GetAttrClass()
print gac.x

程序运行结果为:None

当存在已定义好的值后,不再调用__getattr__而是直接返回定义好的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GetAttrClass(object):
def __init__(self):
pass
def __getattr__(self, item):
if item == 'color':
return 'red'
if __name__ == '__main__':
gac = GetAttrClass()
print gac.color
gac.color = 'green'
print gac.color

程序运行结果为:

1
2
red
green
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GetAttrClass(object):
def __init__(self):
self.color = 'black'
def __getattr__(self, item):
if item == 'color':
return 'red'
if __name__ == '__main__':
gac = GetAttrClass()
print gac.color
gac.color = 'green'
print gac.color

程序运行结果为:

1
2
black
green

当程序定义__getattribute__后,每次引用属性和方法都会调用它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GetAttrClass(object):
def __init__(self):
self.color = 'black'
def __getattribute__(self, item):
if item == 'color':
return 'red'
if __name__ == '__main__':
gac = GetAttrClass()
print gac.color
gac.color = 'green'
print gac.color

程序运行结果为:

1
2
red
red

即便已经显式地设置 gac.color,在获取 gac.color 的值时, 仍将调用 __getattribute__() 方法。如果存在 __getattribute__() 方法,将在每次查找属性和方法时 无条件地调用 它,哪怕在创建实例之后已经显式地设置了属性。

如果定义了类的 __getattribute__() 方法,你可能还想定义一个 __setattr__() 方法,并在两者之间进行协同,以跟踪属性的值。否则,在创建实例之后所设置的值将会消失在黑洞中。

必须特别小心 __getattribute__() 方法,因为 Python 在查找类的方法名称时也将对其进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
class GetAttrClass(object):
def __getattribute__(self, item):
raise AttributeError
def hello(self):
print 'hello world!'
if __name__ == '__main__':
gac = GetAttrClass()
gac.hello()

以上程序报错:

1
2
3
4
5
6
Traceback (most recent call last):
File "/Users/pan/PycharmProjects/test/getattr.py", line 17, in <module>
gac.hello()
File "/Users/pan/PycharmProjects/test/getattr.py", line 9, in __getattribute__
raise AttributeError
AttributeError
  • 该类定义了一个总是引发 AttributeError 异常的 __getattribute__() 方法。没有属性或方法的查询会成功。
  • 调用 gac.hello() 时,Python 将在 GetAttrClass 类中查找 hello() 方法。该查找将执行整个 __getattribute__()方法,因为所有的属性和方法查找都通过__getattribute__() 方法。在此例中, __getattribute__() 方法引发 AttributeError 异常,因此该方法查找过程将会失败,而方法调用也将失败。