久久ER99热精品一区二区-久久精品99国产精品日本-久久精品免费一区二区三区-久久综合九色综合欧美狠狠

博客專欄

EEPW首頁 > 博客 > Python視頻教程之Python的屬性查找分享

Python視頻教程之Python的屬性查找分享

發布人:扣丁學習 時間:2020-11-30 來源:工程師 發布文章

  今天我們了解下python的屬性查找,在Python中,屬性查找(attributelookup)是比較復雜的,特別是涉及到描述符descriptor的時候。首先,我們知道:python中一切都是對象,“everythingisobject”,包括類,類的實例,數字,模塊任何object都是類(classortype)的實例(instance)如果一個descriptor只實現了get方法,我們稱之為non-datadescriptor,如果同時實現了get__set__我們稱之為datadescriptor。

  實例屬性查找


  按照pythondoc,如果obj是某個類的實例,那么obj.name(以及等價的getattr(obj,’name’))首先調用getattribute。如果類定義了getattr方法,那么在getattribute拋出AttributeError的時候就會調用到getattr,而對于描述符(__get__)的調用,則是發生在getattribute內部的。官網文檔是這么描述的


  Theimplementationworksthroughaprecedencechainthatgivesdatadescriptorspriorityoverinstancevariables,instancevariablespriorityovernon-datadescriptors,andassignslowestpriorityto__getattr__()ifprovided.


  obj=Clz(),那么obj.attr順序如下:


  (1)如果“attr”是出現在Clz或其基類的dict中,且attr是datadescriptor,那么調用其get方法,否則


  (2)如果“attr”出現在obj的dict中,那么直接返回obj.dict[‘attr’],否則


  (3)如果“attr”出現在Clz或其基類的dict中


  (3.1)如果attr是non-datadescriptor,那么調用其get方法,否則


  (3.2)返回dict[‘attr’]


  (4)如果Clz有getattr方法,調用getattr方法,否則


  (5)拋出AttributeError


  下面是測試代碼:


  #coding=utf-8


  classDataDescriptor(object):


  def__init__(self,init_value):


  self.value=init_value


  def__get__(self,instance,typ):


  return'DataDescriptor__get__'


  def__set__(self,instance,value):


  print('DataDescriptor__set__')


  self.value=value


  classNonDataDescriptor(object):


  def__init__(self,init_value):


  self.value=init_value


  def__get__(self,instance,typ):


  return('NonDataDescriptor__get__')


  classBase(object):


  dd_base=DataDescriptor(0)


  ndd_base=NonDataDescriptor(0)


  classDerive(Base):


  dd_derive=DataDescriptor(0)


  ndd_derive=NonDataDescriptor(0)


  same_name_attr='attrinclass'


  def__init__(self):


  self.not_des_attr='Iamnotdescriptorattr'


  self.same_name_attr='attrinobject'


  def__getattr__(self,key):


  return'__getattr__withkey%s'%key


  defchange_attr(self):


  self.__dict__['dd_base']='dd_basenowinobjectdict'


  self.__dict__['ndd_derive']='ndd_derivenowinobjectdict'


  defmain():


  b=Base()


  d=Derive()


  print'Deriveobjectdict',d.__dict__


  assertd.dd_base=="DataDescriptor__get__"


  assertd.ndd_derive=='NonDataDescriptor__get__'


  assertd.not_des_attr=='Iamnotdescriptorattr'


  assertd.no_exists_key=='__getattr__withkeyno_exists_key'


  assertd.same_name_attr=='attrinobject'


  d.change_attr()


  print'Deriveobjectdict',d.__dict__


  assertd.dd_base!='dd_basenowinobjectdict'


  assertd.ndd_derive=='ndd_derivenowinobjectdict'


  try:


  b.no_exists_key


  exceptException,e:


  assertisinstance(e,AttributeError)


  if__name__=='__main__':


  main()


  ```


  [python視頻教程](http://www.2xkt.com/python)


  注意第50行,change_attr給實例的__dict__里面增加了兩個屬性。通過上下兩條print的輸出如下:


  ```brush:python


  Deriveobjectdict{‘same_name_attr’:‘attrinobject’,‘not_des_attr’:‘Iamnotdescriptorattr’}


  Deriveobjectdict{‘same_name_attr’:‘attrinobject’,‘ndd_derive’:‘ndd_derivenowinobjectdict‘,‘not_des_attr’:‘Iamnotdescriptorattr’,‘dd_base’:‘dd_basenowinobjectdict‘}


  調用change_attr方法之后,dd_base既出現在類的dict(作為datadescriptor),也出現在實例的dict,因為attributelookup的循序,所以優先返回的還是Clz.__dict__[‘dd_base’]。而ndd_base雖然出現在類的dict,但是因為是nondatadescriptor,所以優先返回obj.__dict__[‘dd_base’]。其他:line48,line56表明了getattr的作用。line49表明obj.__dict__優先于Clz.__dict__


  cached_property例子


  我們再來看看上一文章的這段代碼


  importfunctools,time


  classcached_property(object):


  """Apropertythatisonlycomputedonceperinstanceandthenreplaces


  itselfwithanordinaryattribute.Deletingtheattributeresetsthe


  property."""


  def__init__(self,func):


  functools.update_wrapper(self,func)


  self.func=func


  def__get__(self,obj,cls):


  ifobjisNone:returnself


  value=obj.__dict__[self.func.__name__]=self.func(obj)


  returnvalue


  classTestClz(object):


  @cached_property


  defcomplex_calc(self):


  print'verycomplex_calc'


  returnsum(range(100))


  if__name__=='__main__':


  t=TestClz()


  print'>>>firstcall'


  printt.complex_calc


  print'>>>secondcall'


  printt.complex_calc


  ```


  cached_property是一個non-datadescriptor。在TestClz中,用cached_property裝飾方法complex_calc,返回值是一個descriptor實例,所以在調用的時候沒有使用小括號。


  第一次調用t.complex_calc之前,obj(t)的__dict__中沒有”complex_calc“,根據查找順序第三條,執行cached_property.__get__,這個函數代用緩存的complex_calc函數計算出結果,并且把結果放入obj.__dict__。那么第二次訪問t.complex_calc的時候,根據查找順序,第二條有限于第三條,所以就直接返回obj.__dict__[‘complex_calc’]。bottle的源碼中還有兩個descriptor,非常厲害!


  ##類屬性查找


  前面提到過,類的也是對象,類是元類(metaclass)的實例,所以類屬性的查找順序基本同上。區別在于第二步,由于Clz可能有基類,所以是在Clz及其基類的__dict__”查找“attr,注意這里的查找并不是直接返回clz.__dict__[‘attr’]。具體來說,這第二步分為以下兩種情況:


  (2.1)如果clz.__dict__[‘attr’]是一個descriptor(不管是datadescriptor還是non-datadescriptor),都調用其__get__方法


  (2.2)否則返回clz.__dict__[‘attr’]


  這就解釋了一個很有意思的問題:method與function的問題


  ```brush:python


  >>>classWidget(object):


  ...deffunc(self):


  ...pass


  ...


  >>>w=Widget()


  >>>Widget.__dict__


  dict_proxy({'__dict__':<attribute'__dict__'of'Widget'objects>,'__module__':'__main__','__weakref__':<attribute'__weakref__'of'Widget'objects>,'__doc__':None,'func':<functionfuncat0x7fdc7d0d1668>})


  >>>w.__dict__


  {}


  >>>Widget.__dict__['func']


  <functionfuncat0x7fdc7d0d1668>


  >>>Widget.func


  <unboundmethodWidget.func>


  >>>


  Widget是一個之定義了一個func函數的類,func是類的屬性,這個也可以通過Widget.dict、w.dict看到。Widget.dict[‘func’]返回的是一個function,但Widget.func是一個unboundmethod,即Widget.func并不等同于Widget.dict[‘func’],按照前面的類屬性的訪問順序,我們可以懷疑,func是一個descriptor,這樣才不會走到第2.2這種情況。驗證如下:


  classMaxValDes(object):


  def__init__(self,attr,max_val):


  self.attr=attr


  self.max_val=max_val


  def__get__(self,instance,typ):


  returninstance.__dict__[self.attr]


  def__set__(self,instance,value):


  instance.__dict__[self.attr]=min(self.max_val,value)


  print'MaxValDes__set__',self.attr,instance.__dict__[self.attr]


  classWidget(object):


  a=MaxValDes('a',10)


  def__init__(self):


  self.a=0


  #def__setattr__(self,name,value):


  #self.__dict__[name]=value


  #print'Widget__setattr__',name,self.__dict__[name]


  if__name__=='__main__':


  w0=Widget()


  w0.a=123


  ```


  輸出如下:


  ```brush:python


  MaxValDes__set__a0


  MaxValDes__set__a10


  可以看到,即使Widget的實例也有一個‘a’屬性,但是調用w.a的時候會調用類屬性‘a’(一個descriptor)的set方法。如果不注釋掉第18到第20行,輸出如下


  Widget__setattr__a0


  Widget__setattr__a123


  可以看到,優先調用Widget的setattr方法。因此:對于屬性賦值,obj=Clz(),那么obj.attr=var,按照這樣的順序:


  如果Clz定義了setattr方法,那么調用該方法,否則如果“attr”是出現在Clz或其基類的dict中,且attr是datadescriptor,那么調用其set方法,否則等價調用obj.dict[‘attr’]=var


  最后想要了解更多關于Python發展前景趨勢,請關注扣丁學堂python培訓官網、微信等平臺,扣丁學堂IT職業在線學習教育平臺為您提供最新的Python視頻教程系統,通過千鋒扣丁學堂金牌講師在線錄制的Python視頻教程課程,讓你快速掌握Python從入門到精通開發實戰技能。扣丁學堂Python技術交流群:816572891。

*博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。



關鍵詞:

相關推薦

技術專區

關閉