99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Python學(xué)習(xí)之屬性訪問與描述符詳解

發(fā)布時間:2017-05-02 10:01  回復(fù):0  查看:2515   最后回復(fù):2017-05-02 10:01  
Python開發(fā)中,對于一個對象的屬性訪問,我們一般采用的是點(.) 屬性運算符進(jìn)行操作。例如,有一個類實例對象 foo  ,它有一個 name  屬性,那便可以使用 foo.name  對此屬性進(jìn)行訪問。一般而言,點(.) 屬性運算符比較直觀,也是我們經(jīng)常碰到的一種屬性訪問方式。然而,在點 (.) 屬性運算符的背后卻是別有洞天,值得我們對對象的屬性訪問進(jìn)行探討。
  在進(jìn)行對象屬性訪問的分析之前,我們需要先了解一下對象怎么表示其屬性。為了便于說明,本文以新式類為例。有關(guān)新式類和舊式類的區(qū)別,大家可以查看Python 官方文檔。
   對象的屬性
  Python 中, 一切皆對象 。我們可以給對象設(shè)置各種屬性。先來看一個簡單的例子:
  class Animal(object):
  run = True
  class Dog(Animal):
  fly = False
  def __init__(self, age):
  self.age = age
  def sound(self):
  return "wang wang~"
  上面的例子中,我們定義了兩個類。類 Animal  定義了一個屬性 run  ;類 Dog  繼承自 Animal  ,定義了一個屬性 fly  和兩個函數(shù)。接下來,我們實例化一個對象。對象的屬性可以從特殊屬性 __dict__  中查看。
  實例化一個對象 dog>>> dog = Dog(1)#  查看 dog 對象的屬性 >>> dog.__dict__
  {'age': 1}#  查看類 Dog 的屬性 >>> Dog.__dict__
  dict_proxy({'__doc__': None,
  '__init__':,
  '__module__': '__main__',
  'fly': False,
  'sound':})#  查看類 Animal 的屬性 >>> Animal.__dict__
  dict_proxy({'__dict__':,
  '__doc__': None,
  '__module__': '__main__',
  '__weakref__':,
  'run': True})
  由上面的例子可以看出:屬性在哪個對象上定義,便會出現(xiàn)在哪個對象的 __dict__  中。例如:
  ·  類 Animal  定義了一個屬性 run  ,那這個 run  屬性便只會出現(xiàn)在類 Animal   __dict__ 中,而不會出現(xiàn)在其子類中。
  ·  類 Dog  定義了一個屬性 fly  和兩個函數(shù),那這些屬性和方法便會出現(xiàn)在類 Dog   __dict__  中,同時它們也不會出現(xiàn)在實例的__dict__  中。
  ·  實例對象 dog   __dict__  中只出現(xiàn)了一個屬性 age  ,這是在初始化實例對象的時候添加的,它沒有父類的屬性和方法。
  ·  由此可知: Python 中對象的屬性具有 層次性   ,屬性在哪個對象上定義,便會出現(xiàn)在哪個對象的__dict__  中。
  在這里我們首先了解的是屬性值會存儲在對象的 __dict__  中,查找也會在對象的 __dict__ 中進(jìn)行查找的。至于Python 對象進(jìn)行屬性訪問時,會按照怎樣的規(guī)則來查找屬性值呢?這個問題在后文中進(jìn)行討論。
   對象屬性訪問與特殊方法 __getattribute__
  正如前面所述,Python 的屬性訪問方式很直觀,使用點屬性運算符。在新式類中,對對象屬性的訪問,都會調(diào)用特殊方法__getattribute__  。 __getattribute__  允許我們在訪問對象屬性時自定義訪問行為,但是使用它特別要小心無限遞歸的問題。
  還是以上面的情景為例:
  class Animal(object):
  run = True
  class Dog(Animal):
  fly = False
  def __init__(self, age):
  self.age = age
  重寫 __getattribute__ 。需要注意的是重寫的方法中不能
  使用對象的點運算符訪問屬性,否則使用點運算符訪問屬性時,
  會再次調(diào)用 __getattribute__ 。這樣就會陷入無限遞歸。
  可以使用 super() 方法避免這個問題。
  def __getattribute__(self, key):
  print  "calling __getattribute__\n"
  return super(Dog, self).__getattribute__(key)
  def sound(self):
  return "wang wang~"
  上面的例子中我們重寫了 __getattribute__  方法。注意我們使用了 super()  方法來避免無限循環(huán)問題。下面我們實例化一個對象來說明訪問對象屬性時 __getattribute__  的特性。
  實例化對象 dog>>> dog = Dog(1)#  訪問 dog 對象的 age 屬性
  >>> dog.age
  calling __getattribute__1
  訪問 dog 對象的 fly 屬性 >>> dog.fly
  calling __getattribute__
  False
  訪問 dog 對象的 run 屬性 >>> dog.run
  calling __getattribute__
  True
  訪問 dog 對象的 sound 方法 >>> dog.sound
  calling __getattribute__
  <bound method Dog.sound of <__main__.dog object="" at="" 0x0000000005a90668="">>
  由上面的驗證可知, __getattribute__  是實例對象查找屬性或方法的入口  。實例對象訪問屬性或方法時都需要調(diào)用到__getattribute__  ,之后才會根據(jù)一定的規(guī)則在各個 __dict__  中查找相應(yīng)的屬性值或方法對象,若沒有找到則會調(diào)用__getattr__  (后面會介紹到)。 __getattribute__  Python 中的一個內(nèi)置方法,關(guān)于其底層實現(xiàn)可以查看相關(guān)官方文檔,后面將要介紹的屬性訪問規(guī)則就是依賴于 __getattribute__  的。
   對象屬性控制
  在繼續(xù)介紹后面相關(guān)內(nèi)容之前,讓我們先來了解一下Python 中和對象屬性控制相關(guān)的相關(guān)方法。
  __getattr__(self, name)
  __getattr__  可以用來在當(dāng)用戶試圖訪問一個根本不存在(或者暫時不存在)的屬性時,來定義類的行為。前面講到過,當(dāng)__getattribute__  方法找不到屬性時,最終會調(diào)用 __getattr__  方法。它可以用于捕捉錯誤的以及靈活地處理AttributeError 。只有當(dāng)試圖訪問不存在的屬性時它才會被調(diào)用。
  __setattr__(self, name, value)
  __setattr__  方法允許你自定義某個屬性的賦值行為,不管這個屬性存在與否,都可以對任意屬性的任何變化都定義自己的規(guī)則。關(guān)于 __setattr__  有兩點需要說明:第一,使用它時必須小心,不能寫成類似 self.name = "Tom"  這樣的形式,因為這樣的賦值語句會調(diào)用 __setattr__  方法,這樣會讓其陷入無限遞歸;第二,你必須區(qū)分  對象屬性    類屬性  這兩個概念。后面的例子中會對此進(jìn)行解釋。
  __delattr__(self, name)
  __delattr__  用于處理刪除屬性時的行為。和 __setattr__  方法要注意無限遞歸的問題,重寫該方法時不要有類似 del self.name  的寫法。
  還是以上面的例子進(jìn)行說明,不過在這里我們要重寫三個屬性控制方法。
  class Animal(object):
  run = True
  class Dog(Animal):
  fly = False
  def __init__(self, age):
  self.age = age
  def __getattr__(self, name):
  print "calling __getattr__\n"
  if name == 'adult':
  return True if self.age >= 2 else False
  else:
  raise AttributeError
  def __setattr__(self, name, value):
  print "calling __setattr__"
  super(Dog, self).__setattr__(name, value)
  def __delattr__(self, name):
  print "calling __delattr__"
  super(Dog, self).__delattr__(name)
  以下進(jìn)行驗證。首先是 __getattr__ :
  創(chuàng)建實例對象 dog>>> dog = Dog(1)
  calling __setattr__#  檢查一下 dog Dog __dict__>>> dog.__dict__
  {'age': 1}>>> Dog.__dict__
  dict_proxy({'__delattr__':,
  '__doc__': None,
  '__getattr__':,
  '__init__':,
  '__module__': '__main__',
  '__setattr__':,
  'fly': False})
  獲取 dog age 屬性 >>> dog.age1#  獲取 dog adult 屬性。 由于 __getattribute__ 沒有找到相應(yīng)的屬性,所以調(diào)用 __getattr__ 。 >>> dog.adult
  calling __getattr__
  False
  調(diào)用一個不存在的屬性 name , __getattr__ 捕獲 AttributeError 錯誤 >>> dog.name
  calling __getattr__
  Traceback (most recent call last):
  File "", line 1, in <module>
  File "", line 10, in __getattr__
  AttributeError
  可以看到,屬性訪問時,當(dāng)訪問一個不存在的屬性時觸發(fā) __getattr__  ,它會對訪問行為進(jìn)行控制。接下來是 __setattr__ 
   dog.age 賦值,會調(diào)用 __setattr__ 方法 >>> dog.age = 2
  calling __setattr__>>> dog.age2
  先調(diào)用 dog.fly 時會返回 False ,這時因為 Dog 類屬性中有 fly 屬性; 之后再給 dog.fly 賦值,觸發(fā) __setattr__ 方法。 >>> dog.fly
  False>>> dog.fly = True
  calling __setattr__
  再次查看 dog.fly 的值以及 dog Dog __dict__;#  可以看出對 dog 對象進(jìn)行賦值,會在 dog 對象的 __dict__ 中添加了一條對象屬性; # 然而, Dog 類屬性沒有發(fā)生變化 注意: dog 對象和 Dog 類中都有 fly 屬性,訪問時會選擇哪個呢? >>> dog.fly
  True>>> dog.__dict__
  {'age': 2, 'fly': True}>>> Dog.__dict__
  dict_proxy({'__delattr__':,
  '__doc__': None,
  '__getattr__':,
  '__init__':,
  '__module__': '__main__',
  '__setattr__':,
  'fly': False})
  實例對象的 __setattr__  方法可以定義屬性的賦值行為,不管屬性是否存在。當(dāng)屬性存在時,它會改變其值;當(dāng)屬性不存在時,它會添加一個對象屬性信息到對象的 __dict__  中,然而這并不改變類的屬性。從上面的例子可以看出來。
  最后,看一下 __delattr__ 
  由于上面的例子中我們?yōu)?/span> dog 設(shè)置了 fly 屬性,現(xiàn)在刪除它觸發(fā) __delattr__ 方法 >>> del dog.fly
  calling __delattr__#  再次查看 dog 對象的 __dict__ ,發(fā)現(xiàn)和 fly 屬性相關(guān)的信息被刪除 >>> dog.__dict__
  {'age': 2}
   描述符
  描述符是Python 2.2  版本中引進(jìn)來的新概念。描述符一般用于實現(xiàn)對象系統(tǒng)的底層功能, 包括綁定和非綁定方法、類方法、靜態(tài)方法特特性等。關(guān)于描述符的概念,官方并沒有明確的定義,可以在網(wǎng)上查閱相關(guān)資料。這里我從自己的認(rèn)識談一些想法,如有不當(dāng)之處還請包涵。
  在前面我們了解了對象屬性訪問和行為控制的一些特殊方法,例如 __getattribute__  、 __getattr__  __setattr__  、__delattr__  。以我的理解來看,這些方法應(yīng)當(dāng)具有屬性的" 普適性 " ,可以用于屬性查找、設(shè)置、刪除的一般方法,也就是說所有的屬性都可以使用這些方法實現(xiàn)屬性的查找、設(shè)置、刪除等操作。但是,這并不能很好地實現(xiàn)對某個具體屬性的訪問控制行為。例如,上例中假如要實現(xiàn) dog.age  屬性的類型設(shè)置(只能是整數(shù)),如果單單去修改 __setattr__  方法滿足它,那這個方法便有可能不能支持其他的屬性設(shè)置。
  在類中設(shè)置屬性的控制行為不能很好地解決問題,Python 給出的方案是: __getattribute__ 、 __getattr__  __setattr__ 、 __delattr__  等方法用來實現(xiàn)屬性查找、設(shè)置、刪除的一般邏輯,而對屬性的控制行為就由屬性對象來控制。這里單獨抽離出來一個屬性對象,在屬性對象中定義這個屬性的查找、設(shè)置、刪除行為。這個屬性對象就是描述符。
  描述符對象一般是作為其他類對象的屬性而存在。在其內(nèi)部定義了三個方法用來實現(xiàn)屬性對象的查找、設(shè)置、刪除行為。這三個方法分別是:
  · get (self, instance, owner) :定義當(dāng)試圖取出描述符的值時的行為。
  · set (self, instance, value) :定義當(dāng)描述符的值改變時的行為。
  · delete (self, instance) :定義當(dāng)描述符的值被刪除時的行為。
  其中:instance 為把描述符對象作為屬性的對象實例;
  owner instance 的類對象。
  以下以官方的一個例子進(jìn)行說明:
  class RevealAccess(object):
  def __init__(self, initval=None, name='var'):
  self.val = initval
  self.name = name
  def __get__(self, obj, objtype):
  print 'Retrieving', self.name
  return self.val
  def __set__(self, obj, val):
  print 'Updating', self.name
  self.val = val
  class MyClass(object):
  x = RevealAccess(10, 'var "x"')
  y = 5
  以上定義了兩個類。其中 RevealAccess  類的實例是作為 MyClass  類屬性 x  的值存在的。而且 RevealAccess 類定義了__get__  、 __set__  方法,它是一個描述符對象。注意,描述符對象的 __get__   __set__  方法中使用了諸如 self.val  self.val = val  等語句,這些語句會調(diào)用 __getattribute__   __setattr__  等方法,這也說明了__getattribute__  __setattr__  等方法在控制訪問對象屬性上的一般性(一般性是指對于所有屬性它們的控制行為一致),以及 __get__   __set__ 等方法在控制訪問對象屬性上的特殊性(特殊性是指它針對某個特定屬性可以定義不同的行為)。
  以下進(jìn)行驗證:
  創(chuàng)建 Myclass 類的實例 m>>> m = MyClass()
  查看 m MyClass __dict__>>> m.__dict__
  {}>>> MyClass.__dict__
  dict_proxy({'__dict__':,
  '__doc__': None,
  '__module__': '__main__',
  '__weakref__':,
  'x':<__main__.revealaccess at="" 0x5130080="">,
  'y': 5})
  訪問 m.x 。會先觸發(fā) __getattribute__ 方法 由于 x 屬性的值是一個描述符,會觸發(fā)它的 __get__ 方法 >>> m.x
  Retrieving var "x"10
  設(shè)置 m.x 的值。對描述符進(jìn)行賦值,會觸發(fā)它的 __set__ 方法 __set__ 方法中還會觸發(fā) __setattr__ 方法( self.val = val >>> m.x = 20
  Updating var "x"
  再次訪問 m.x>>> m.x
  Retrieving var "x"20
  查看 m MyClass __dict__ ,發(fā)現(xiàn)這與對描述符賦值之前一樣。 這一點與一般屬性的賦值不同,可參考上述的 __setattr__ 方法。 之所以前后沒有發(fā)生變化,是因為變化體現(xiàn)在描述符對象上, 而不是實例對象 m 和類 MyClass 上。 >>> m.__dict__
  {}>>> MyClass.__dict__
  dict_proxy({'__dict__':,
  '__doc__': None,
  '__module__': '__main__',
  '__weakref__':,
  'x':<__main__.revealaccess at="" 0x5130080="">,
  'y': 5})
  上面的例子對描述符進(jìn)行了一定的解釋,不過對描述符還需要更進(jìn)一步的探討和分析,這個工作先留待以后繼續(xù)進(jìn)行。
  最后,還需要注意一點:描述符有數(shù)據(jù)描述符和非數(shù)據(jù)描述符之分。
  ·  只要至少實現(xiàn) __get__   __set__  、 __delete__  方法中的一個就可以認(rèn)為是描述符;
  ·  只實現(xiàn) __get__  方法的對象是非數(shù)據(jù)描述符,意味著在初始化之后它們只能被讀取;
  ·  同時實現(xiàn) __get__   __set__  的對象是數(shù)據(jù)描述符,意味著這種屬性是可讀寫的。
   屬性訪問的優(yōu)先規(guī)則
  在以上的討論中,我們一直回避著一個問題,那就是屬性訪問時的優(yōu)先規(guī)則。我們了解到,屬性一般都在__dict__  中存儲,但是在訪問屬性時,在對象屬性、類屬型、基類屬性中以怎樣的規(guī)則來查詢屬性呢?以下對Python 中屬性訪問的規(guī)則進(jìn)行分析。
  由上述的分析可知,屬性訪問的入口點是 __getattribute__  方法。它的實現(xiàn)中定義了Python 中屬性訪問的優(yōu)先規(guī)則。 Python 官方文檔中對 __getattribute__  的底層實現(xiàn)有相關(guān)的介紹,本文暫時只是討論屬性查找的規(guī)則,相關(guān)規(guī)則可見下圖:
Python學(xué)習(xí)之屬性訪問與描述符詳解
Python 屬性查找
  上圖是查找 b.x  這樣一個屬性的過程。在這里要對此圖進(jìn)行簡單的介紹:
  1.  查找屬性的第一步是搜索基類列表,即 type(b).__mro__  ,直到找到該屬性的第一個定義,并將該屬性的值賦值給 descr
  2.  判斷 descr  的類型。它的類型可分為數(shù)據(jù)描述符、非數(shù)據(jù)描述符、普通屬性、未找到等類型。若 descr 為數(shù)據(jù)描述符,則調(diào)用 desc.__get__(b, type(b))  ,并將結(jié)果返回,結(jié)束執(zhí)行。否則進(jìn)行下一步;
  3.  如果 descr  為非數(shù)據(jù)描述符、普通屬性、未找到等類型,則查找實例b 的實例屬性,即 b.__dict__  。如果找到,則將結(jié)果返回,結(jié)束執(zhí)行。否則進(jìn)行下一步;
  4.  如果在 b.__dict__  未找到相關(guān)屬性,則重新回到 descr  值的判斷上。
  ·  若 descr  為非數(shù)據(jù)描述符,則調(diào)用 desc.__get__(b, type(b))  ,并將結(jié)果返回,結(jié)束執(zhí)行;
  ·  若 descr  為普通屬性,直接返回結(jié)果并結(jié)束執(zhí)行;
  ·  若 descr  為空(未找到),則最終拋出 AttributeError  異常,結(jié)束查找。

來源: 稀土掘金
您還未登錄,請先登錄

熱門帖子

最新帖子

?