python中的描述符能夠用來定義觸發自動執行的代碼,它像是一個對象屬性操做(訪問、賦值、刪除)的代理類同樣。前面介紹過的property是描述符的一種。python
大體流程是這樣的:git
__get__()
、__set__()
、__delete__()
方法attr = D()
__get__()
、__set__()
、__delete__()
方法簡言之,就是建立一個描述符類,它的實例對象做爲另外一個類的屬性。github
要定義描述符類很簡單,只要某個類中包含了下面一個或多個方法,就算是知足描述符協議,就是描述符類,就能夠做爲屬性操做的代理器。數據結構
class Descriptor(): def __get__(self, instance, owner):... def __set__(self, instance, value):... def __delete__(self, instance):...
須要注意的是,__get__
的返回值須要是屬性值或拋異常,另外兩個方法要返回None。函數
還需注意的是不要把__delete__
和__del__
搞混了,前者是實現描述符協議的一個方法,後者是對象銷燬函數(也常稱爲析構函數)。測試
先無論這幾個方法中的參數,看一個示例先:ui
class Descriptor(): def __get__(self, instance, owner): print("self: %s\ninstance: %s\nowner: %s" % (self, instance, owner)) class S: # 描述符的示例對象做爲S的屬性 attr = Descriptor() s1 = S() s1.attr # 訪問對象屬性 print("-" * 30) S.attr # 訪問類屬性
輸出結果:url
self: <__main__.Descriptor object at 0x030C02D0> instance: <__main__.S object at 0x030C0AB0> owner: <class '__main__.S'> ------------------------------ self: <__main__.Descriptor object at 0x030C02D0> instance: None owner: <class '__main__.S'>
不難看出,在訪問類S中的屬性attr時,表示訪問描述符類的實例對象,它會自動調用描述符類中的__get__
方法。代理
在這個方法中,3個參數self、instance、owner分別對應的內容從結果中已經顯示出來了。它們之間有如下等價關係:code
s1.attr -> Descriptor.__get__(S.attr, s1, S) S.attr -> Descriptor.__get__(S.attr, None, S)
因此,這裏解釋下__get__(self, instance, owner)
中的三個參數:
再解釋下這裏相關的幾個角色:
Descriptor
:是描述符類,也是代理者S
:是另外一個類,是託管類、客戶類,也就是參數中的ownerattr = Descriptor()
:是描述符的實例對象,attr是託管類的屬性,也就參數中的selfs1
:是託管類實例對象,也就是參數中的instance按照descriptor的功能,大概能夠用上面的方式去定義各個角色。固然,角色的定義沒什麼限制。
當定義了一個類後,能夠訪問、賦值、刪除它的屬性,這些操做也一樣適用於它的實例對象。
例如Foo類:
class Foo(): ... f = Foo() a = f.bar # 訪問屬性 f.bar = b # 賦值屬性 del f.bar # 刪除屬性
decriptor發揮做用的時候就在於執行這3類操做的時候:
__get__
__set__
__delete__
考慮一下:若是x所屬的類中已經定義了__getattr__
、__setattr__
、__delattr__
會如何,是描述符類中的先生效,仍是x自身所屬類的這幾個方法會生效。再繼續考慮,若是x所屬類沒有定義,但它的父類定義了這幾個方法,誰會生效。可自行測試或者參考個人下一篇文章。
假設如今有一個Student類,須要記錄stuid、name、score一、score二、score3信息。
class Student(): def __init__(self, stuid, name, score1, score2, score3): self.stuid = stuid self.name = name self.score1 = score1 self.score2 = score2 self.score3 = score3 def returnMe(self): return "%s, %s, %i, %i, %i" % ( self.stuid, self.name, self.score1, self.score2, self.score3) stu = Student("20101120", "malong", 67, 77, 88) print(stu.returnMe())
可是如今有個需求,要求score1-score3
的數值範圍只能是0-100分。
因而修改__init__()
:
class Student(): def __init__(self, stuid, name, score1, score2, score3): self.stuid = stuid self.name = name if 0 <= score1 <= 100: self.score1 = score1 else: raise ValueError("score not in [0,100]") if 0 <= score2 <= 100: self.score2 = score2 else: raise ValueError("score not in [0,100]") if 0 <= score3 <= 100: self.score3 = score3 else: raise ValueError("score not in [0,100]")
這個修改對於初始化Student對象時有效,但Python中屬性的賦值太過自由,以後能夠隨意賦值:
stu = Student("20101120", "malong", 67, 77, 88) stu.score1 = -23 print(stu.returnMe())
使用Property或者自定義的getter、setter或運算符__getattr__
、__setattr__
重載都能解決上面的問題,保證沒法賦值超出0到100範圍內的數值。
class Student(): def __init__(self, stuid, name, score1, score2, score3): self.stuid = stuid self.name = name self._score1 = score1 self._score2 = score2 self._score3 = score3 def get_score1(self): return self._score1 def set_score1(self, score): if 0 <= score <= 100: self._score1 = score else: raise ValueError("score not in [0,100]") def get_score2(self): return self._score2 def set_score2(self, score): if 0 <= score <= 100: self._score2 = score else: raise ValueError("score not in [0,100]") def get_score3(self): return self._score3 def set_score3(self, score): if 0 <= score <= 100: self._score3 = score else: raise ValueError("score not in [0,100]") score1 = property(get_score1, set_score1) score2 = property(get_score2, set_score2) score3 = property(get_score3, set_score3) def returnMe(self): return "%s, %s, %i, %i, %i" % ( self.stuid, self.name, self.score1, self.score2, self.score3)
下面測試時將拋出異常。
stu = Student("20101120", "malong", 67, 77, 88) print(stu.returnMe()) stu.score1 = -23
但很顯然,上面的重複代碼太多了。
若是使用descriptor,將很容易解決上面的問題。只需將score一、score二、score3交給描述符類託管便可。
from weakref import WeakKeyDictionary class Score(): """ score should in [0,100] """ def __init__(self): self.score = WeakKeyDictionary() #self.score = {} def __get__(self, instance, owner): return self.score[instance] def __set__(self, instance, value): if 0 <= value <= 100: self.score[instance] = value else: raise ValueError("score not in [0,100]") class Student(): # 託管屬性定義在類級別上 score1 = Score() score2 = Score() score3 = Score() def __init__(self, stuid, name, score1, score2, score3): self.stuid = stuid self.name = name self.score1 = score1 self.score2 = score2 self.score3 = score3 def returnMe(self): return "%s, %s, %i, %i, %i" % ( self.stuid, self.name, self.score1, self.score2, self.score3) stu = Student("20101120", "malong", 67, 77, 88) print(stu.returnMe()) stu.score1 = -23
很明顯地,它們的代碼被完整地複用了。這裏score一、score二、score3被描述符類Score託管了,這3個分值分別被放進了Score實例對象的dict中(是單獨存放它們仍是使用dict數據結構來保存,取決於你)。
另外,上面使用了弱引用的字典,由於每一個屬性只在描述符對象中才會被用上,爲了保證Student對象被銷燬的時候能釋放這些資源,因此採用弱引用,避免出現內存泄漏。