亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Python > 正文

Python 的描述符 descriptor詳解

2020-01-04 17:42:27
字體:
來源:轉載
供稿:網友
Python中包含了許多內建的語言特性,它們使得代碼簡潔且易于理解。這些特性包括列表/集合/字典推導式,屬性(property)、以及裝飾器(decorator)。對于大部分特性來說,這些“中級”的語言特性有著完善的文檔,并且易于學習。但是這里有個例外,那就是描述符。
 

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

那么什么是 descriptor 呢?

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

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

引言

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

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'      

很簡單一個示例,看起來運行的不錯:

xiaoming = MathScore(10, 90)xiaoming.scoreOut[3]: 90xiaoming.std_idOut[4]: 10xiaoming.check()Out[5]: 'pass'

但是會有一個問題,比如手一抖錄入了一個負分數,那么他就得悲劇的掛了:

xiaoming = MathScore(10, -90)xiaoming.scoreOut[8]: -90xiaoming.check()Out[9]: 'failed'

這顯然是一個嚴重的問題,怎么能讓一個數學 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'          

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

xiaoming = MathScore(10, -90)Traceback (most recent call last): File "<ipython-input-12-6faad631790d>", line 1, in <module>  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  # 無法判斷出錯誤

對于大多數童鞋,這個問題 so easy 的啦:將 score 變為私有,從而禁止 xiaoming.score 這樣的直接調用,增加一個 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

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

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

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

Property 類

先不管 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'            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 進行讀寫了:

xiaoming = MathScore(10, 90)xiaoming.scoreOut[30]: 90xiaoming.score = 80xiaoming.scoreOut[32]: 80xiaoming.score = -90Traceback (most recent call last): File "<ipython-input-33-aed7397ed552>", line 1, in <module>  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 的參數:

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

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

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

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

另外,Property 還有個裝飾器語法糖 @property,其所實現的功能與 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 實例的工作方式了,那么問題又來了:它是怎么實現的?
事實上 Property 確實是基于 descriptor 而實現的,下面進入我們的正題 descriptor 吧!

descriptor 描述符

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

class NonNegative():    def __init__(self):    pass  def __get__(self, ist, cls):    return 'descriptor get: ' + str(ist.__score ) #這里加上字符描述便于看清調用  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 類,并在其內實現了__get__、__set__方法,然后在 MathScore 類中實例化了一個 NonNegative 的實例 score,注意!??!重要的事情說三遍:score 實例是 MathScore 的類屬性?。?!類屬性?。?!類屬性!?。∵@個 Mathscore.score 屬性同上面 Property 的 score 實例的功能是一樣的,只不過 Mathscore.score 調用的 get、set 并不定義在 Mathscore 內,而是定義在 NonNegative 類中,而 NonNegative 類就是一個 descriptor 對象!

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

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

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

xiaoming = MathScore(10, 90)xiaoming.scoreOut[67]: 'descriptor get: 90'xiaoming.score = 80descriptor set: 80wangerma = MathScore(11, 70)wangerma.scoreOut[70]: 'descriptor get: 70'wangerma.score = 60Out[70]: descriptor set: 60wangerma.scoreOut[73]: 'descriptor get: 60'xiaoming.scoreOut[74]: 'descriptor get: 80'xiaoming.score = -90ValueError: Score can't be negative number!

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

注意看 __get__、__set__ 的參數

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

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

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

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

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

看個栗子:假如不僅要判斷學生的分數是否為負數,而且還要判學生的學號是否為負值,使用 property 的實現方式是這樣子的:

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 實例僅能針對宿主類實例的單個屬性,如果需要對多個屬性進行控制,則必須定義多個 Property 實例, 這真是太蛋疼了!
但是自定義 descriptor 可以很好的解決這個問題,看下實現:

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):    #這里并未創建實例屬性 std_id 和 score, 而是調用 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__ 內終于沒了 if ,代碼也比上面的簡潔不少,但是功能一個不少,且實例之間不會相互影響:

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

Property 實例本質上是借助類屬性,變向對實例屬性進行操作,而 NonNegative 實例則是完全通過類屬性模擬實例屬性,因此實例屬性其實根本不存在;

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

這三點看的似懂非懂,沒關系,來個比喻:

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

因此,實例對應的屬性,壓根不在實例自己的作用域內,而是在類作用域的籃子里,只不過我們可以通過 xiaoming.score 這樣的方式進行操作而已,所以其實際的調用的邏輯是這樣的:下圖右側的實例分別通過紅線和黑線對score和std_id 進行操作,他們首先通過類調用相應的類屬性,然后類屬性通過對應的 descriptor 實例作用域對操作進行處理,并返回給類屬性相應結果,最后讓實例感知到。

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

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

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


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲品质视频自拍网| 久久精品国产欧美亚洲人人爽| 久久天堂电影网| 久久久久久久91| 日本精品va在线观看| 亚洲自拍偷拍视频| 国产一区二区三区在线视频| 欧美黑人一级爽快片淫片高清| 欧美怡春院一区二区三区| 日韩在线观看网址| 国内精品小视频在线观看| 日韩成人av网| 精品国模在线视频| 久久久91精品国产| 成人在线国产精品| 美女999久久久精品视频| 欧美日韩国产综合新一区| 久久韩国免费视频| 日韩高清免费观看| 91色琪琪电影亚洲精品久久| 欧美天堂在线观看| 国产91ⅴ在线精品免费观看| 久久免费视频在线| 日韩精品亚洲元码| 在线精品视频视频中文字幕| 免费成人高清视频| 日韩av在线影视| 免费91在线视频| 久久视频在线播放| 国产精品扒开腿做爽爽爽的视频| 日韩av一卡二卡| 亚洲电影中文字幕| 国产一区二区日韩精品欧美精品| 亚洲曰本av电影| 免费不卡在线观看av| 在线精品高清中文字幕| 日本精品一区二区三区在线播放视频| 国产精品入口免费视频一| 亚洲伊人久久大香线蕉av| 日本高清视频精品| 欧美夜福利tv在线| 日韩美女在线看| 精品国产自在精品国产浪潮| 久久综合久久八八| 中文字幕日本精品| 亚洲天堂av电影| 久久黄色av网站| 欧美午夜女人视频在线| 日韩免费观看网站| 欧美老女人www| 久久久中文字幕| 最近2019中文字幕大全第二页| 综合网中文字幕| 亚洲人成在线电影| 成人av色在线观看| 中文字幕日韩欧美| 欧美夜福利tv在线| 91精品91久久久久久| 国产成人av在线播放| 欧美精品在线播放| 欧美一区二区三区免费视| 国产精品午夜国产小视频| 狠狠久久五月精品中文字幕| 久久免费成人精品视频| 欧美性xxxxxxx| 在线播放日韩av| www.久久撸.com| 国产成人精品亚洲精品| 欧美精品午夜视频| 最近2019中文字幕mv免费看| 不卡毛片在线看| 色爱精品视频一区| xxxxx成人.com| 北条麻妃99精品青青久久| 亚洲人成人99网站| 国产精品视频一区国模私拍| 色哟哟亚洲精品一区二区| 欧美视频在线视频| 91av中文字幕| 欧美富婆性猛交| 中文亚洲视频在线| 国产视频自拍一区| 亚洲午夜精品久久久久久性色| 日韩中文字幕久久| 38少妇精品导航| 亚洲国产精品一区二区三区| 久久久精品美女| 日本伊人精品一区二区三区介绍| 91午夜在线播放| 亚洲人午夜精品免费| 久久成人精品视频| 久久国产精品99国产精| 国产一区二区三区三区在线观看| 91久久久久久久久| 精品福利樱桃av导航| 一区二区三区无码高清视频| 亚洲国产精品免费| 久久久久久久999精品视频| 亚洲japanese制服美女| 欧美成人精品一区| 91精品久久久久久久久不口人| 亚洲综合在线中文字幕| 亚洲激情小视频| 好吊成人免视频| 国产999在线观看| 亚洲男人天堂手机在线| 亚洲最新视频在线| 日韩av中文字幕在线播放| 国产精品小说在线| 亚洲人线精品午夜| 91chinesevideo永久地址| 欧美成人网在线| 久久大大胆人体| 国产成人+综合亚洲+天堂| 欧美黑人性生活视频| 亚洲人成网站999久久久综合| 91精品久久久久久久久不口人| 国产999在线观看| 亚洲跨种族黑人xxx| 成人欧美一区二区三区在线湿哒哒| 精品视频久久久久久久| 国产亚洲欧美日韩美女| 精品亚洲夜色av98在线观看| 精品在线欧美视频| 日韩精品久久久久久久玫瑰园| www国产91| 国产欧美欧洲在线观看| 亚洲精品乱码久久久久久金桔影视| 久久人体大胆视频| 68精品久久久久久欧美| 欧美成人中文字幕在线| 日韩av一区在线| 国产精品高清网站| 亚洲日韩中文字幕在线播放| 久久久久久高潮国产精品视| 欧美福利视频在线| 麻豆一区二区在线观看| 国产97在线播放| 久久夜精品va视频免费观看| 久久99久久久久久久噜噜| 久久久亚洲国产天美传媒修理工| 日本乱人伦a精品| 久久人人爽人人爽人人片av高请| 美日韩精品免费视频| 91人成网站www| 亚洲第一视频网站| 国产激情综合五月久久| 国产一区深夜福利| 亚洲欧美国产日韩中文字幕| 国产亚洲人成a一在线v站| 久久久久久久久久久久久久久久久久av| 4388成人网| 国产精品www网站| 国产精品久久综合av爱欲tv| 国产欧美亚洲精品| 亚洲三级 欧美三级| 欧美性xxxxx极品娇小| 激情av一区二区| 欧美在线一区二区视频| 午夜精品久久17c| 欧美老女人性生活| 欧美黑人巨大精品一区二区| 精品视频在线播放免| 亚洲欧洲午夜一线一品|