Python对象的属性访问过程详解
只想回答一个问题:当编译器要读取obj.field时,发生了什么?
看似简单的属性访问,其过程还蛮曲折的.总共有以下几个step:
1.如果obj本身(一个instance)有这个属性,返回.如果没有,执行step2
2.如果obj的class有这个属性,返回.如果没有,执行step3.
3.如果在objclass的父类有这个属性,返回.如果没有,继续执行3,直到访问完所有的父类.如果还是没有,执行step4.
4.执行obj.__getattr__方法.
通过以下代码可以验证:
classA(object):
a='a'
classB(A):
b='b'
classC(B):
class_field='classfield'
def__getattr__(self,f):
print('Method{}.__getattr__hasbeencalled.'.format(
self.__class__.__name__))
returnf
c=C()
printc.a
printc.b
printc.class_field
printc.c
输出:
a b classfield MethodC.__getattr__hasbeencalled. c
PS:python里的attribute与property不同,当使用了property里,property的解析优先级最高.详见blog:从attribute到property.
补充知识:深入理解python对象及属性
类属性和实例属性
首先来看看类属性和类实例的属性在python中如何存储,通过__dir__方法来查看对象的属性
>>>classTest(object): pass >>>test=Test() #查看类属性 >>>dir(Test) ['__class__','__delattr__','__dict__','__doc__','__format__', '__getattribute__','__hash__','__init__','__module__', '__new__','__reduce__','__reduce_ex__','__repr__', '__setattr__','__sizeof__','__str__','__subclasshook__', '__weakref__'] #查看实例属性 >>>dir(test) ['__class__','__delattr__','__dict__','__doc__','__format__', '__getattribute__','__hash__','__init__','__module__', '__new__','__reduce__','__reduce_ex__','__repr__', '__setattr__','__sizeof__','__str__','__subclasshook__', '__weakref__']
我们主要看一个属性__dict__,因为__dict__保存的对象的属性,看下面一个例子
>>>classSpring(object):
...season="thespringofclass"
...
#查看Spring类保存的属性
>>>Spring.__dict__
dict_proxy({'__dict__':,
'season':'thespringofclass',
'__module__':'__main__',
'__weakref__':,
'__doc__':None})
#通过两种方法访问类属性
>>>Spring.__dict__['season']
'thespringofclass'
>>>Spring.season
'thespringofclass'
发现__dict__有个'season'键,这就是这个类的属性,其值就是类属性的数据.
接来看,看看它的实例属性
>>>s=Spring()
#实例属性的__dict__是空的
>>>s.__dict__
{}
#其实是指向的类属性
>>>s.season
'thespringofclass'
#建立实例属性
>>>s.season="thespringofinstance"
#这样,实例属性里面就不空了。这时候建立的实例属性和类属性重名,并且把它覆盖了
>>>s.__dict__
{'season':'thespringofinstance'}
>>>s.__dict__['season']
'thespringofinstance'
>>>s.season
'thespringofinstance'
#类属性没有受到实例属性的影响
>>>Spring.__dict__['season']
'thespringofclass'
>>>Spring.__dict__
dict_proxy({'__dict__':,'season':'thespringofclass','__module__':'__main__','__weakref__':,'__doc__':None})
#如果将实例属性删除,又会调用类属性
>>>dels.season
>>>s.__dict__
{}
>>>s.season
'thespringofclass'
#自定义实例属性,对类属性没有影响
>>>s.lang="python"
>>>s.__dict__
{'lang':'python'}
>>>s.__dict__['lang']
'python'
#修改类属性
>>>Spring.flower="peach"
>>>Spring.__dict__
dict_proxy({'__module__':'__main__',
'flower':'peach',
'season':'thespringofclass',
'__dict__':,'__weakref__':,'__doc__':None})
>>>Spring.__dict__['flower']
'peach'
#实例中的__dict__并没有变化
>>>s.__dict__
{'lang':'python'}
#实例中找不到flower属性,调用类属性
>>>s.flower
'peach'
下面看看类中包含方法,__dict__如何发生变化
#定义类
>>>classSpring(object):
...deftree(self,x):
...self.x=x
...returnself.x
...
#方法tree在__dict__里面
>>>Spring.__dict__
dict_proxy({'__dict__':,
'__weakref__':,
'__module__':'__main__',
'tree':,
'__doc__':None})
>>>Spring.__dict__['tree']
#建立实例,但是__dict__中没有方法
>>>t=Spring()
>>>t.__dict__
{}
#执行方法
>>>t.tree("xiangzhangshu")
'xiangzhangshu'
#实例方法(t.tree('xiangzhangshu'))的第一个参数(self,但没有写出来)绑定实例t,透过self.x来设定值,即给t.__dict__添加属性值。
>>>t.__dict__
{'x':'xiangzhangshu'}
#如果没有将x赋值给self的属性,而是直接return,结果发生了变化
>>>classSpring(object):
...deftree(self,x):
...returnx
>>>s=Spring()
>>>s.tree("liushu")
'liushu'
>>>s.__dict__
{}
需要理解python中的一个观点,一切都是对象,不管是类还是实例,都可以看成是对象,符合object.attribute,都会有自己的属性
使用__slots__优化内存使用
默认情况下,python在各个实例中为名为__dict__的字典里存储实例属性,而字典会消耗大量内存(字典要使用底层散列表提升访问速度),通过__slots__类属性,在元组中存储实例属性,不用字典,从而节省大量内存
#在类中定义__slots__属性就是说这个类中所有实例的属性都在这儿了,如果几百万个实例同时活动,能节省大量内存
>>>classSpring(object):
...__slots__=("tree","flower")
...
#仔细看看dir()的结果,还有__dict__属性吗?没有了,的确没有了。也就是说__slots__把__dict__挤出去了,它进入了类的属性。
>>>dir(Spring)
['__class__','__delattr__','__doc__','__format__','__getattribute__','__hash__','__init__','__module__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__slots__','__str__','__subclasshook__','flower','tree']
>>>Spring.__slots__
('tree','flower')
#实例化
>>>t=Spring()
>>>t.__slots__
('tree','flower')
#通过类赋予属性值
>>>Spring.tree="liushu"
#tree这个属性是只读的,实例不能修改
>>>t.tree="guangyulan"
Traceback(mostrecentcalllast):
File"",line1,in
AttributeError:'Spring'objectattribute'tree'isread-only
>>>t.tree
'liushu'
#对于用类属性赋值的属性,只能用来修改
>>>Spring.tree="guangyulan"
>>>t.tree
'guangyulan'
#对于没有用类属性赋值的属性,可以通过实例来修改
>>>t.flower="haitanghua"
>>>t.flower
'haitanghua'
#实例属性的值并没有传回到类属性,你也可以理解为新建立了一个同名的实例属性
>>>Spring.flower
#如果再给类属性赋值
>>>Spring.flower="ziteng"
>>>t.flower
'ziteng'
如果使用的当,__slots__可以显著节省内存,按需要注意一下问题
在类中定义__slots__之后,实例不能再有__slots__所列名称之外的其他属性
每个子类都要定义__slots__熟悉,因为解释器会忽略继承__slots__属性
如果不把__werkref__加入__slots__,实例不能作为弱引用的目标
属性的魔术方法
来看几个魔术方法
__setattr__(self,name,value):如果要给name赋值,就调用这个方法。 __getattr__(self,name):如果name被访问,同时它不存在的时候,此方法被调用。 __getattribute__(self,name):当name被访问时自动被调用(注意:这个仅能用于新式类),无论name是否存在,都要被调用。 __delattr__(self,name):如果要删除name,这个方法就被调用。 >>>classA(object): ...def__getattr__(self,name): ...print"Youusegetattr" ...def__setattr__(self,name,value): ...print"Youusesetattr" ...self.__dict__[name]=value #a.x,按照本节开头的例子,是要报错的。但是,由于在这里使用了__getattr__(self,name)方法,当发现x不存在于对象的__dict__中的时候,就调用了__getattr__,即所谓“拦截成员”。 >>>a=A() >>>a.x Youusegetattr #给对象的属性赋值时候,调用了__setattr__(self,name,value)方法,这个方法中有一句self.__dict__[name]=value,通过这个语句,就将属性和数据保存到了对象的__dict__中 >>>a.x=7 Youusesetattr #测试__getattribute__(self,name) >>>classB(object): ...def__getattribute__(self,name): ...print"youareuseinggetattribute" ...returnobject.__getattribute__(self,name) #返回的内容用的是returnobject.__getattribute__(self,name),而没有使用returnself.__dict__[name]。因为如果用这样的方式,就是访问self.__dict__,只要访问这个属性,就要调用`getattribute``,这样就导致了无限递归 #访问不存在的成员,可以看到,已经被__getattribute__拦截了,虽然最后还是要报错的。 >>>b=B() >>>b.y youareuseinggetattribute Traceback(mostrecentcalllast): File"",line1,in File" ",line4,in__getattribute__ AttributeError:'B'objecthasnoattribute'y'
Property函数
porperty可以作为装饰器使用把方法标记为特性
classVector(object): def__init__(self,x,y): #使用两个前导下划线,把属性标记为私有 self.__x=float(x) self.__y=float(y) #porperty装饰器把读值方法标记为特性 @property defx(self): returnself.__x @property defy(self): returnself.__y vector=Vector(3,4) print(vector.x,vector.y)
使用property可以将函数封装为属性
classRectangle(object): """ thewidthandlengthofRectangle """ def__init__(self): self.width=0 self.length=0 defsetSize(self,size): self.width,self.length=size defgetSize(self): returnself.width,self.length if__name__=="__main__": r=Rectangle() r.width=3 r.length=4 printr.getSize()#(3,4) r.setSize((30,40)) printr.width#30 printr.length#40
这段代码可以正常运行,但是属性的调用方式可以改进,如下:
classRectangle(object): """ thewidthandlengthofRectangle """ def__init__(self): self.width=0 self.length=0 defsetSize(self,size): self.width,self.length=size defgetSize(self): returnself.width,self.length #使用property方法将函数封装为属性,更优雅 size=property(getSize,setSize) if__name__=="__main__": r=Rectangle() r.width=3 r.length=4 printr.size#(30,40) r.size=30,40 printr.width#30 printr.length#40
使用魔术方法实现:
classNewRectangle(object): def__init__(self): self.width=0 self.length=0 def__setattr__(self,name,value): ifname=='size': self.width,self,length=value else: self.__dict__[name]=value def__getattr__(self,name): ifname=='size': returnself.width,self.length else: raiseAttrubuteErrir if__name__=="__main__": r=Rectangle() r.width=3 r.length=4 printr.size#(30,40) r.size=30,40 printr.width#30 printr.length#40
属性的获取顺序
最后我们来看看熟悉的获得顺序:通过实例获取其属性,如果在__dict__中有相应的属性,就直接返回其结果;如果没有,会到类属性中找。
看下面一个例子:
classA(object): author="qiwsir" def__getattr__(self,name): ifname!="author": return"fromstartertomaster." if__name__=="__main__": a=A() printa.author#qiwsir printa.lang#fromstartertomaster.
当a=A()后,并没有为实例建立任何属性,或者说实例的__dict__是空的。但是如果要查看a.author,因为实例的属性中没有,所以就去类属性中找,发现果然有,于是返回其值“qiwsir”。但是,在找a.lang的时候,不仅实例属性中没有,类属性中也没有,于是就调用了__getattr__()方法。在上面的类中,有这个方法,如果没有__getattr__()方法呢?如果没有定义这个方法,就会引发AttributeError,这在前面已经看到了。
以上这篇Python对象的属性访问过程详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。