Python中几种属性访问的区别与用法详解
起步
在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作。例如,有一个类实例对象foo,它有一个name属性,那便可以使用foo.name对此属性进行访问。一般而言,点(.)属性运算符比较直观,也是我们经常碰到的一种属性访问方式。
python的提供一系列和属性访问有关的特殊方法:__get__,__getattr__,__getattribute__,__getitem__。本文阐述它们的区别和用法。
属性的访问机制
一般情况下,属性访问的默认行为是从对象的字典中获取,并当获取不到时会沿着一定的查找链进行查找。例如a.x的查找链就是,从a.__dict__['x'],然后是type(a).__dict__['x'],再通过type(a)的基类开始查找。
若查找链都获取不到属性,则抛出AttributeError异常。
__getattr__方法
__getattr__函数的作用:如果属性查找(attributelookup)在实例以及对应的类中(通过__dict__)失败,那么会调用到类的__getattr__函数,如果没有定义这个函数,那么抛出AttributeError异常。由此可见,__getattr__一定是作用于属性查找的最后一步,兜底。
这个方法是当对象的属性不存在是调用。如果通过正常的机制能找到对象属性的话,不会调用__getattr__方法。
classA: a=1 def__getattr__(self,item): print('__getattr__call') returnitem t=A() print(t.a) print(t.b) #output 1 __getattr__call b
__getattribute__方法
这个方法会被无条件调用。不管属性存不存在。如果类中还定义了__getattr__,则不会调用__getattr__()方法,除非在__getattribute__方法中显示调用__getattr__()或者抛出了AttributeError。
classA: a=1 def__getattribute__(self,item): print('__getattribute__call') raiseAttributeError def__getattr__(self,item): print('__getattr__call') returnitem t=A() print(t.a) print(t.b)
所以一般情况下,为了保留__getattr__的作用,__getattribute__()方法中一般返回父类的同名方法:
def__getattribute__(self,item): returnobject.__getattribute__(self,item)
使用基类的方法来获取属性能避免在方法中出现无限递归的情况。
__get__方法
这个方法比较简单说明,它与前面的关系不大。
如果一个类中定义了__get__(),__set__()或__delete__()中的任何方法。则这个类的对象称为描述符。
classDescri(object): def__get__(self,obj,type=None): print("callget") def__set__(self,obj,value): print("callset") classA(object): x=Descri() a=A() a.__dict__['x']=1#不会调用__get__ a.x#调用__get__
如果查找的属性是在描述符对象中,则这个描述符会覆盖上文说的属性访问机制,体现在查找链的不同,而这个行文也会因为调用的不同而稍有不一样:
- 如果调用是对象实例(题目中的调用方式),a.x则转换为调用:。type(a).__dict__['x'].__get__(a,type(a))
- 如果调用的是类属性,A.x则转换为:A.__dict__['x'].__get__(None,A)
- 其他情况见文末参考资料的文档
__getitem__方法
这个调用也属于无条件调用,这点与__getattribute__一致。区别在于__getitem__让类实例允许[]运算,可以这样理解:
- __getattribute__适用于所有.运算符;
- __getitem__适用于所有[]运算符。
classA(object): a=1 def__getitem__(self,item): print('__getitem__call') returnitem t=A() print(t['a']) print(t['b'])
如果仅仅想要对象能够通过[]获取对象属性可以简单的:
def__getitem(self,item): returnobject.__getattribute__(self,item)
总结
当这几个方法同时出现可能就会扰乱你了。我在网上看到一份示例还不错,稍微改了下:
classC(object): a='abc' def__getattribute__(self,*args,**kwargs): print("__getattribute__()iscalled") returnobject.__getattribute__(self,*args,**kwargs) #return"haha" def__getattr__(self,name): print("__getattr__()iscalled") returnname+"fromgetattr" def__get__(self,instance,owner): print("__get__()iscalled",instance,owner) returnself def__getitem__(self,item): print('__getitem__call') returnobject.__getattribute__(self,item) deffoo(self,x): print(x) classC2(object): d=C() if__name__=='__main__': c=C() c2=C2() print(c.a) print(c.zzzzzzzz) c2.d print(c2.d.a) print(c['a'])
可以结合输出慢慢理解,这里还没涉及继承关系呢。总之,每个以__get为前缀的方法都是获取对象内部数据的钩子,名称不一样,用途也存在较大的差异,只有在实践中理解它们,才能真正掌握它们的用法。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。