(轉)面向對象(深刻)|python描述器詳解

原文:https://zhuanlan.zhihu.com/p/32764345html

 

# 相似函數的形式 class A: def __init__(self, name, score): self.name = name # 普通屬性 self.score = score def getscore(self): return self._score def setscore(self, value): print('setting score here') if isinstance(value, int): self._score = value else: print('please input an int') score = property(getscore, setscore) a = A('Bob',90) a.name # 'Bob' a.score # 90 a.score = 'bob' # please input an int

分析上述調用score的過程python

  • 初始化時即開始訪問score,發現有兩個選項,一個是屬性,另外一個是property(getscore, setscore)對象,由於後者中定義了__get____set__方法,所以是一個資料描述器,具備比屬性更高的優先級,因此這裏就訪問了描述器
  • 由於初始化時是對屬性進行設置,因此自動調用了描述器的__set__方法
  • __set__中對fset屬性進行檢查,這裏即傳入的setscore,不是None,因此調用了fsetsetscore方法,這就實現了設置屬性時使用自定義函數進行檢查的目的
  • __get__也是同樣,查詢score時,調用__get__方法,觸發了getscore方法

下面是另外一種使用property的方法緩存

# 裝飾器形式,即引言中的形式 class A: def __init__(self, name, score): self.name = name # 普通屬性 self.score = score @property def score(self): print('getting score here') return self._score @score.setter def score(self, value): print('setting score here') if isinstance(value, int): self._score = value else: print('please input an int') a = A('Bob',90) # a.name # 'Bob' # a.score # 90 # a.score = 'bob' # please input an int

下面進行分析框架

  • 在第一種使用方法中,是將函數做爲傳入property中,因此能夠想到是否能夠用裝飾器來封裝
  • get部分很簡單,訪問score時,加上裝飾器變成訪問property(score)這個描述器,這個score也做爲fget參數傳入__get__中指定調用時的操做
  • 而set部分就不行了,因而有了setter等方法的定義
  • 使用了propertysetter裝飾器的兩個方法的命名都仍是score,通常同名的方法後面的會覆蓋前面的,因此調用時調用的是後面的setter裝飾器處理過的score,是以若是兩個裝飾器定義的位置調換,將沒法進行屬性賦值操做。
  • 而調用setter裝飾器的score時,面臨一個問題,裝飾器score.setter是什麼呢?是scoresetter方法,而score是什麼呢,不是下面定義的這個score,由於那個score只至關於參數傳入。自動向其餘位置尋找有沒有現成的score,發現了一個,是property修飾過的score,這是個描述器,根據property的定義,裏面確實有一個setter方法,返回的是property類傳入fset後的結果,仍是一個描述器,這個描述器傳入了fgetfset,這就是最新的score了,之後實例只要調用或修改score,使用的都是這個描述器
  • 若是還有del則裝飾器中的score找到的是setter處理過的score,最新的score就會是三個函數都傳入的score
  • 對最新的score的調用及賦值刪除都跟前面同樣了

property的原理就講到這裏,從它的定義咱們能夠知道它其實就是將咱們設置的檢查等函數傳入get set等方法中,讓咱們能夠自由對屬性進行操做。它是一個框架,讓咱們能夠方便傳入其餘操做,當不少對象都要進行相同操做的話,重複就是不免的。若是想要避免重複,只有本身寫一個相似property的框架,這個框架不是傳入咱們但願的操做了,而是就把這些操做放在框架裏面,這個框架由於只能實現一種操做而不具備普適性,可是卻能大大減小當前問題代碼重複問題ide

下面使用描述器定義了Checkint類以後,會發現A類簡潔了很是多函數

class Checkint: def __init__(self, name): self.name = name def __get__(self, instance, owner): if instance is None: return self else: return instance.__dict__[self.name] def __set__(self, instance, value): if isinstance(value, int): instance.__dict__[self.name] = value else: print('please input an integer') # 相似函數的形式 class A: score = Checkint('score') age = Checkint('age') def __init__(self, name, score, age): self.name = name # 普通屬性 self.score = score self.age = age a = A('Bob', 90, 30) a.name # 'Bob' a.score # 90 # a.score = 'bob' # please input an int # a.age='a' # please input an integer

描述器的應用

由於我本人也剛剛學描述器不久,對它的應用還不是很是瞭解,下面只列舉我如今能想到的它有什麼用,之後若是想到其餘的再補充ui

  • 首先是上文提到的,它是實例方法、靜態方法、類方法、property的實現原理
  • 當訪問屬性、賦值屬性、刪除屬性,出現冗餘操做,或者苦思沒法找到答案時,能夠求助於描述器
  • 具體使用1:緩存。好比調用一個類的方法要計算比較長的時間,這個結果還會被其餘方法反覆使用,咱們不想每次使用和這個相關的函數都要把這個方法從新運行一遍,因而能夠設計出第一次計算後將結果緩存下來,之後調用都使用存下來的結果。只要使用描述器在__get__方法中,在判斷語句下,obj.__dict__[self.name] = value。這樣每次再調用這個方法都會從這個字典中取得值,而不是從新運行這個方法。(例子來源最後的那個例子)

參考資料

參考網頁以下spa

  • 官網的中文翻譯,給出了描述器功能的總體框架及一些實例
  • 官網英文
  • 簡書文章,主要講解訪問描述器順序,靜態方法、類方法和實例方法下的訪問狀況
  • 簡書文章,對官網的@Property細節解讀
  • 一篇譯文能夠再看看他下面附的參考資料(不要先看這篇,這篇有點深,並且我的認爲他有些實現方法捨近求遠)
  • 若是想看更多文章,搜索時注意:搜索「描述器」獲得的文章高度重複,基本上就是上面幾篇了,搜「描述符」會找到更多文章
相關文章
相關標籤/搜索