简述
结合代码区分一下python的几个魔术方法__get__
, __getattr__
, __getattribute__
, __getitem__
操作环境
win7_64 + Python 2.7.16 + IPython 5.9.0
__dict__
首先我们通过obj.attr
的方式访问对象属性的时候,在python内部,实际是访问了obj.__dict__[attr]
,__dict__
有文章单独说明,这里只需要简单理解,一般情况下,我们访问一个对象的属性,其实是在读取这个属性的__dict__
字典:
In [7]: class Test(object):
...: def __init__(self):
...: self.a = 1
...: self.b = 2
...:
# 定义了一个Test类的对象t
In [8]: t = Test()
# 虽然t没有显示定义__dict__属性,但是python会自动生成,它是一个字典
In [9]: t.__dict__
Out[9]: {'a': 1, 'b': 2}
In [10]: t.a
Out[10]: 1
# t.a的访问方式等价于t.__dict__['a']的访问方式
In [11]: t.__dict__['a']
Out[11]: 1
__getattr__
如果一个类定义了此方法,当访问一个类不存在的属性的时候,会访问此方法并且返回对应的值,如果不存在对应的访问,应该抛出AttributeError
In [10]: class Test(object):
...: def __init__(self):
...: self.a = 1
...:
...: def __getattr__(self, name): # name参数就是访问的属性
...: print name
...: return 2
...:
...: t = Test()
...:
In [11]: t.a
Out[11]: 1
In [12]: t.b
b
Out[12]: 2
In [13]: t.c
c
Out[13]: 2
t
对象有a
属性,所以获取到了对应的值,但是b
和c
均不是对象t
的属性,故访问了__getattr__
方法,参数name
获取到了对应的属性名,并且都返回2.
注意下面的代码
class Test(object):
def __init__(self):
self.a = 1
def __getattr__(self, name):
return self.name
t = Test()
t.b
当我们在访问t.b
的时候,t
对象没有b
属性,会访问__getattr__
,此方法再次通过self.name
的方式尝试访问b
属性,还是不存在,同样走到了__getattr__
,这样会造成无限循环,直到最后抛出RuntimeError: maximum recursion depth exceeded
异常,我们可以通过下面的方式来避免出现这种情况:
class Test(object):
def __init__(self):
self.a = 1
def __getattr__(self, name):
# 有对应的属性,我们才访问它的属性
if hasattr(self, name):
# getattr访问属性的方式和t.b的方式是一样的
return getattr(self, name)
else:
# 否则直接抛出异常
raise AttributeError()
t = Test()
t.b
__getattribute__
只要定义了此函数,任何属性访问,会无条件的调用此方法,也就是屏蔽了__dict__
和__getattr__
In [10]: class Test(object):
...: def __init__(self):
...: self.a = 1
...:
...: def __getattr__(self, name):
...: print '__getattr__'
...:
...: def __getattribute__(self, name):
...: print '__getattribute__'
...:
...: t = Test()
...: t.b
...:
__getattribute__
# 同样t.__dict__也是在访问t的属性,也访问到了__getattribute__方法
In [11]: t.__dict__
__getattribute__
- 它和
__getattr__
一样,如果不想有对应的属性,应该抛出AttributeError
,而且也需要注意递归访问的情况出现- 此方法只针对新式类有效,老式类此方法是不会生效的,除非主动调用
__getitem__
这个方法最简单,定义此方法的类一般是模拟容器的行为,比如模拟字典,列表的访问
In [12]: class Test(object):
...: def __init__(self):
...: self.a = 1
...:
...: def __getitem__(self, key):
...: print key
...:
...: t = Test()
...: t['a']
...: t[1]
...:
a
1
- 其中key在列表里面是整数,在字典里面可以是字符串
- 如果key的类型不正确,应该抛出TypeError
- 如果key索引错误抛出IndexError
- 如果key在字典不存在则抛出KeyError
__get__
此方法涉及到描述符的概念,由于篇幅较长,会单独写一篇文章记录。
总结
- 新式类只要定义了
__getattribute__
,则会无条件访问,但老式类不生效 - 定义了
__getattr__
,如果访问的属性不存在的时候,才会访问此方法 -
__getitem__
最简单,用于模拟容器的访问方式 -
__getattribute__
和__getattr__
都应避免进入递归调用,并且如果访问的属性不应该存在的情况下,需要抛出AttributeError
异常 -
getattr(obj, attr)
的方式访问属性和obj.attr
的方式是一样的,都等同于obj.__dict__[attr]