亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Python 的描述符 descriptor詳解

系統(tǒng) 1663 0

Python 在 2.2 版本中引入了descriptor(描述符)功能,也正是基于這個功能實現(xiàn)了新式類(new-styel class)的對象模型,同時解決了之前版本中經(jīng)典類 (classic class) 系統(tǒng)中出現(xiàn)的多重繼承中的 MRO(Method Resolution Order) 問題,另外還引入了一些新的概念,比如 classmethod, staticmethod, super, Property 等。因此理解 descriptor 有助于更好地了解 Python 的運(yùn)行機(jī)制。

那么什么是 descriptor 呢?

簡而言之:descriptor 就是一類實現(xiàn)了__get__(), __set__(), __delete__()方法的對象。

Orz...如果你瞬間頓悟了,那么請收下我的膝蓋;
O_o!...如果似懂非懂,那么恭喜你!說明你潛力很大,咱們可以繼續(xù)挖掘:

引言

對于陌生的事物,一個具體的栗子是最好的學(xué)習(xí)方式,首先來看這樣一個問題:假設(shè)我們給一次數(shù)學(xué)考試創(chuàng)建一個類,用于記錄每個學(xué)生的學(xué)號、數(shù)學(xué)成績、以及提供一個用于判斷是否通過考試的check 函數(shù):

            
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    self.score = score

  def check(self):
    if self.score >= 60:
      return 'pass'
    else:
      return 'failed'      


          

很簡單一個示例,看起來運(yùn)行的不錯:

            
xiaoming = MathScore(10, 90)

xiaoming.score
Out[3]: 90

xiaoming.std_id
Out[4]: 10

xiaoming.check()
Out[5]: 'pass'


          

但是會有一個問題,比如手一抖錄入了一個負(fù)分?jǐn)?shù),那么他就得悲劇的掛了:

            
xiaoming = MathScore(10, -90)

xiaoming.score
Out[8]: -90

xiaoming.check()
Out[9]: 'failed'


          

這顯然是一個嚴(yán)重的問題,怎么能讓一個數(shù)學(xué) 90+ 的孩子掛科呢,于是乎一個簡單粗暴的方法就誕生了:

            
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    if score < 0:
      raise ValueError("Score can't be negative number!")
    self.score = score

  def check(self):
    if self.score >= 60:
      return 'pass'
    else:
      return 'failed'          

          

?
上面再類的初始化函數(shù)中增加了負(fù)數(shù)判斷,雖然不夠優(yōu)雅,甚至有點拙劣,但這在實例初始化時確實工作的不錯:

            
xiaoming = MathScore(10, -90)

Traceback (most recent call last):

 File "
            
              ", line 1, in 
              
                
  xiaoming = MathScore(10, -90)

 File "C:/Users/xu_zh/.spyder2-py3/temp.py", line 14, in __init__
  raise ValueError("Score can't be negative number!")

ValueError: Score can't be negative number!


              
            
          

OK, 但我們還無法阻止實例對 score 的賦值操作,畢竟修改成績也是常有的事:

            
xiaoming = MathScore(10, 90)

xiaoming = -10  # 無法判斷出錯誤


          

對于大多數(shù)童鞋,這個問題 so easy 的啦:將 score 變?yōu)樗接校瑥亩?xiaoming.score 這樣的直接調(diào)用,增加一個 get_score 和 set_score 用于讀寫:

            
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    if score < 0:
      raise ValueError("Score can't be negative number!")
    self.__score = score

  def check(self):
    if self.__score >= 60:
      return 'pass'
    else:
      return 'failed'      
    
  def get_score(self):
    return self.__score
  
  def set_score(self, value):
    if value < 0:
      raise ValueError("Score can't be negative number!")
    self.__score = value


          

這確實是種常見的解決方法,但是不得不說這簡直丑爆了:

調(diào)用成績再也不能使用 xiaoming.score 這樣自然的方式,需要使用 xiaoming.get_score() ,這看起來像口吃在說話!
還有那反人類的下劃線和括號...那應(yīng)該只出現(xiàn)在計算機(jī)之間竊竊私語之中...
賦值也無法使用 xiaoming.score = 80, 而需使用 xiaoming.set_score(80), 這對數(shù)學(xué)老師來說,太 TM 不自然了 !!!

作為一門簡潔優(yōu)雅的編程語言,Python 是不會坐視不管的,于是其給出了 Property 類:

Property 類

先不管 Property 是啥,咱先看看它是如何簡潔優(yōu)雅的解決上面這個問題的:

            
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    if score < 0:
      raise ValueError("Score can't be negative number!")
    self.__score = score

  def check(self):
    if self.__score >= 60:
      return 'pass'
    else:
      return 'failed'      
    
  def __get_score__(self):
    return self.__score
  
  def __set_score__(self, value):
    if value < 0:
      raise ValueError("Score can't be negative number!")
    self.__score = value
    
  score = property(__get_score__, __set_score__)


          

與上段代碼相比,主要是在最后一句實例化了一個 property 實例,并取名為 score, 這個時候,我們就能如此自然的對 instance.__score 進(jìn)行讀寫了:

            
xiaoming = MathScore(10, 90)

xiaoming.score
Out[30]: 90

xiaoming.score = 80

xiaoming.score
Out[32]: 80

xiaoming.score = -90
Traceback (most recent call last):

 File "
            
              ", line 1, in 
              
                
  xiaoming.score = -90

 File "C:/Users/xu_zh/.spyder2-py3/temp.py", line 28, in __set_score__
  raise ValueError("Score can't be negative number!")

ValueError: Score can't be negative number!


              
            
          

WOW~~一切工作正常!
嗯,那么問題來了:它是怎么工作的呢?
先看下 property 的參數(shù):

class property(fget=None, fset=None, fdel=None, doc=None)? #拷貝自 Python 官方文檔
它的工作方式:

實例化 property 實例(我知道這是句廢話);
調(diào)用 property 實例(比如xiaoming.score)會直接調(diào)用 fget,并由 fget 返回相應(yīng)值;
對 property 實例進(jìn)行賦值操作(xiaoming.score = 80)則會調(diào)用 fset,并由 fset 定義完成相應(yīng)操作;
刪除 property 實例(del xiaoming),則會調(diào)用 fdel 實現(xiàn)該實例的刪除;
doc 則是該 property 實例的字符說明;
fget/fset/fdel/doc 需自定義,如果只設(shè)置了fget,則該實例為只讀對象;
這看起來和本篇開頭所說的 descriptor 的功能非常相似,讓我們回顧一下 descriptor:

“descriptor 就是一類實現(xiàn)了__get__(), __set__(), __delete__()方法的對象。”

@~@ 如果你這次又秒懂了,那么請再次收下我的膝蓋 Orz...

另外,Property 還有個裝飾器語法糖 @property,其所實現(xiàn)的功能與 property() 完全一樣:

            
class MathScore():
  
  def __init__(self, std_id, score):
    self.std_id = std_id
    if score < 0:
      raise ValueError("Score can't be negative number!")
    self.__score = score

  def check(self):
    if self.__score >= 60:
      return 'pass'
    else:
      return 'failed'      
  
  @property  
  def score(self):
    return self.__score
  
  @score.setter
  def score(self, value):  #注意方法名稱要與上面一致,否則會失效
    if value < 0:
      raise ValueError("Score can't be negative number!")
    self.__score = value


          

我們知道了 property 實例的工作方式了,那么問題又來了:它是怎么實現(xiàn)的?
事實上 Property 確實是基于 descriptor 而實現(xiàn)的,下面進(jìn)入我們的正題 descriptor 吧!

descriptor 描述符

照樣先不管 descriptor 是啥,咱們還是先看栗子,對于上面 Property 實現(xiàn)的功能,我們可以通過自定義的 descriptor 來實現(xiàn):

            
class NonNegative():
  
  def __init__(self):
    pass

  def __get__(self, ist, cls):
    return 'descriptor get: ' + str(ist.__score ) #這里加上字符描述便于看清調(diào)用

  def __set__(self, ist, value):
    if value < 0:
      raise ValueError("Score can't be negative number!")
    print('descriptor set:', value)
    ist.__score = value
    
class MathScore():
  
  score = NonNegative()  

  def __init__(self, std_id, score):
    self.std_id = std_id
    if score < 0:
      raise ValueError("Score can't be negative number!")
    self.__score = score
    
  def check(self):
    if self.__score >= 60:
      return 'pass'
    else:
      return 'failed'      


          

我們新定義了一個 NonNegative 類,并在其內(nèi)實現(xiàn)了__get__、__set__方法,然后在 MathScore 類中實例化了一個 NonNegative 的實例 score,注意!!!重要的事情說三遍:score 實例是 MathScore 的類屬性!!!類屬性!!!類屬性!!!這個 Mathscore.score 屬性同上面 Property 的 score 實例的功能是一樣的,只不過 Mathscore.score 調(diào)用的 get、set 并不定義在 Mathscore 內(nèi),而是定義在 NonNegative 類中,而 NonNegative 類就是一個 descriptor 對象!

納尼? NonNegative 類的定義中可沒見到半個 “descriptor” 的字樣,怎么就成了 descriptor 對象???

淡定! 重要的事情這里只說一遍:任何實現(xiàn) __get__,__set__ 或 __delete__ 方法中一至多個的類,就是 descriptor 對象。所以 NonNegative 自然是一個 descriptor 對象。

那么 descriptor 對象與普通類比有什么特別之處呢? 先不急,來看看上端代碼的效果:

            
xiaoming = MathScore(10, 90)

xiaoming.score
Out[67]: 'descriptor get: 90'

xiaoming.score = 80
descriptor set: 80

wangerma = MathScore(11, 70)

wangerma.score
Out[70]: 'descriptor get: 70'

wangerma.score = 60
Out[70]: descriptor set: 60

wangerma.score
Out[73]: 'descriptor get: 60'

xiaoming.score
Out[74]: 'descriptor get: 80'

xiaoming.score = -90

ValueError: Score can't be negative number!


          

可以發(fā)現(xiàn),MathScore.score 雖然是一個類屬性,但它卻可以通過實例的進(jìn)行賦值,且面對不同的 MathScore 實例 xiaoming、wangerma 的賦值和調(diào)用,并不會產(chǎn)生沖突!因此看起來似乎更類似于 MathScore 的實例屬性,但與實例屬性不同的是它并不通過 MathScore 實例的讀寫方法操作值,而總是通過 NonNegative 實例的 __get__ 和 __set__ 對值進(jìn)行操作,那么它是怎么做到這點的?

注意看 __get__、__set__ 的參數(shù)

?def __get__(self, ist, cls):? #self:descriptor 實例本身(如 Math.score),ist:調(diào)用 score 的實例(如 xiaoming),cls:descriptor 實例所在的類(如MathScore)
??????? ...

??? def __set__(self, ist, value):? #score 就是通過這些傳入的 ist 、cls 參數(shù),實現(xiàn)對 MathScore 及其具體實例屬性的調(diào)用和改寫的
??????? ...
OK, 現(xiàn)在我們基本搞清了 descriptor 實例是如何實現(xiàn)對宿主類的實例屬性進(jìn)行模擬的。事實上 Property 實例的實現(xiàn)方式與上面的 NonNegative 實例類似。那么我們既然有了 Propery,為什么還要去自定義 descriptor 呢?

答案在于:更加逼真的模擬實例屬性(想想 MathScore.__init__里面那惡心的判斷語句),還有最重要的是:代碼重用!!!

簡而言之:通過單個 descriptor 對象,可以更加逼真的模擬實例屬性,并且可以實現(xiàn)對宿主類實例的多個實例屬性進(jìn)行操作。

O.O! 如果你又秒懂了,那么你可以直接跳到下面寫評論了...

看個栗子:假如不僅要判斷學(xué)生的分?jǐn)?shù)是否為負(fù)數(shù),而且還要判學(xué)生的學(xué)號是否為負(fù)值,使用 property 的實現(xiàn)方式是這樣子的:

            
class MathScore():
  
  def __init__(self, std_id, score):
    if std_id < 0:
      raise ValueError("Can't be negative number!")
    self.__std_id = std_id
    if score < 0:
      raise ValueError("Can't be negative number!")
    self.__score = score

  def check(self):
    if self.__score >= 60:
      return 'pass'
    else:
      return 'failed'      
  
  @property  
  def score(self):
    return self.__score
  
  @score.setter
  def score(self, value):
    if value < 0:
      raise ValueError("Can't be negative number!")
    self.__score = value
  
  @property
  def std_id(self):
    return self.__std_id

  @std_id.setter
  def std_id(self, idnum):
    if idnum < 0:
      raise ValueError("Can't be negative nmuber!")
    self.__std_id = idnum


          

Property 實例最大的問題是:

無法影響宿主類實例的初始化,所以咱必須在__init__ 加上那丑惡的 if ...
單個 Property 實例僅能針對宿主類實例的單個屬性,如果需要對多個屬性進(jìn)行控制,則必須定義多個 Property 實例, 這真是太蛋疼了!
但是自定義 descriptor 可以很好的解決這個問題,看下實現(xiàn):

            
class NonNegative():
  
  def __init__(self):
    self.dic = dict()

  def __get__(self, ist, cls):
    print('Description get', ist)
    return self.dic[ist]

  def __set__(self, ist, value):
    print('Description set', ist, value)
    if value < 0:
      raise ValueError("Can't be negative number!")
    self.dic[ist] = value
    
class MathScore():
  
  score = NonNegative()  
  std_id = NonNegative()  
  
  def __init__(self, std_id, score):
    #這里并未創(chuàng)建實例屬性 std_id 和 score, 而是調(diào)用 MathScore.std_id 和 MathScore.score
    
    self.std_id = std_id
    self.score = score 
    
  def check(self):
    if self.score >= 60:
      return 'pass'
    else:
      return 'failed'   


          

哈哈~! MathScore.__init__ 內(nèi)終于沒了 if ,代碼也比上面的簡潔不少,但是功能一個不少,且實例之間不會相互影響:

事實上,MathScore 多個實例的同一個屬性,都是通過單個 MathScore 類的相應(yīng)類屬性(也即 NonNegative 實例)操作的,這同 property 一致,但它又是怎么克服 Property 的兩個不足的呢?秘訣有三個:

Property 實例本質(zhì)上是借助類屬性,變向?qū)嵗龑傩赃M(jìn)行操作,而 NonNegative 實例則是完全通過類屬性模擬實例屬性,因此實例屬性其實根本不存在;

NonNegative 實例使用字典記錄每個 MathScore 實例及其對應(yīng)的屬性值,其中 key 為 MathScore 實例名:比如 score 實例就是使用 dic = {‘Zhangsan':50, ‘Lisi':90} 記錄每個實例對應(yīng)的 score 值,從而確保可以實現(xiàn)對 MathScore 實例屬性的模擬;
MathScore 通過在__init__內(nèi)直接調(diào)用類屬性,從而實現(xiàn)對實例屬性初始化賦值的模擬,而 Property 則不可能,因為 Property 實例(也即MathScore的類屬性)是真實的操作 MathScore 實例傳入的實例屬性以達(dá)到目的,但如果在初始化程序中傳入的不是實例屬性,而是類屬性(也即 Property 實例本身),則會陷入無限遞歸(PS:想一下如果將前一個property 實例實現(xiàn)中的self.__score 改成這里的 self.score 會發(fā)生什么)。

這三點看的似懂非懂,沒關(guān)系,來個比喻:

每個 descriptor 實例(MathScore.score 和 MathScore.std_id)都是類作用域里的一個籃子,籃子里放著寫著每個 MathScore 實例名字的盒子(‘zhangsan','lisi‘),同一個籃子里的盒子只記錄同樣屬性的值(比如score籃子里的盒子只記錄分?jǐn)?shù)值),當(dāng) MathScore 的實例對相應(yīng)屬性進(jìn)行操作時,則找到對應(yīng)的籃子,取出標(biāo)有該實例名字的盒子,并對其進(jìn)行操作。

因此,實例對應(yīng)的屬性,壓根不在實例自己的作用域內(nèi),而是在類作用域的籃子里,只不過我們可以通過 xiaoming.score 這樣的方式進(jìn)行操作而已,所以其實際的調(diào)用的邏輯是這樣的:下圖右側(cè)的實例分別通過紅線和黑線對score和std_id 進(jìn)行操作,他們首先通過類調(diào)用相應(yīng)的類屬性,然后類屬性通過對應(yīng)的 descriptor 實例作用域?qū)Σ僮鬟M(jìn)行處理,并返回給類屬性相應(yīng)結(jié)果,最后讓實例感知到。

看到這里,很多童鞋可能不淡定了,因為大家都知道在 Python 中采取 xiaoming.score = 10 這樣的賦值方式,如果 xiaoming 沒有 score 這樣的實例屬性,必定會自動創(chuàng)建該實例屬性,怎么會去調(diào)用 MathScore 的 score 呢?

首先,要鼓掌!!! 給想到這點的童鞋點贊!!!其實上面在說 Property 的時候這個問題就產(chǎn)生了。

其次,Python 為了實現(xiàn) discriptor 確實對屬性的調(diào)用順序做出了相應(yīng)的調(diào)整,這些將會“Python 的 descriptor(下)”中介紹。


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 99久久精品久久久久久婷婷 | 可以免费观看的一级毛片 | 日本欧美一区二区三区在线 | 久久久久久久久亚洲 | 丁香婷婷综合网 | 国产精品欧美亚洲韩国日本 | 国产亚洲精品成人久久网站 | 亚洲国产精品线观看不卡 | 天天做夜夜做 | 日日舔夜夜操 | 亚洲性图第一页 | 黄视频福利 | 国产视频在线一区 | 欧美一区视频 | se色成人亚洲综合 | 四虎在线精品免费高清在线 | 久久久噜噜噜久噜久久 | 人人夜| 中文字幕精品一区二区三区视频 | 狠狠色噜噜狠狠狠狠97影音先锋 | 国产欧美日韩精品a在线观看高清 | 精品国产免费观看一区 | 日韩中文字幕高清在线专区 | 午夜影院免费在线观看 | 亚洲日本一区二区三区 | 天天干天天干 | 99久久免费视频在线观看 | 国产精品视频999 | 国产三级久久久精品三级 | 久久精品人人做人人爱爱 | 色八a级在线观看 | 日韩啊啊啊 | 成年人网站在线 | 俄罗斯午夜影院 | 激情国产视频 | 中文字幕在线观看一区二区 | 国产精品日韩一区二区三区 | 亚洲精品短视频 | 天天操天天噜 | 最新国产午夜精品视频成人 | 妇女bbw奶水bbw|