Descriptor基礎(chǔ)
python中的描述符可以用來(lái)定義觸發(fā)自動(dòng)執(zhí)行的代碼,它像是一個(gè)對(duì)象屬性操作(訪(fǎng)問(wèn)、賦值、刪除)的代理類(lèi)一樣。前面介紹過(guò)的property是描述符的一種。
大致流程是這樣的:
- 定義一個(gè)描述符類(lèi)D,其內(nèi)包含一個(gè)或多個(gè)
__get__() 、__set__() 、__delete__() 方法
- 將描述符類(lèi)D的實(shí)例對(duì)象d賦值給另一個(gè)要代理的類(lèi)中某個(gè)屬性attr,即
attr = D()
- 之后訪(fǎng)問(wèn)、賦值、刪除attr屬性,將會(huì)自動(dòng)觸發(fā)描述符類(lèi)中的
__get__() 、__set__() 、__delete__() 方法
簡(jiǎn)言之,就是創(chuàng)建一個(gè)描述符類(lèi),它的實(shí)例對(duì)象作為另一個(gè)類(lèi)的屬性。
要定義描述符類(lèi)很簡(jiǎn)單,只要某個(gè)類(lèi)中包含了下面一個(gè)或多個(gè)方法,就算是滿(mǎn)足描述符協(xié)議,就是描述符類(lèi),就可以作為屬性操作的代理器。
class Descriptor():
def __get__(self, instance, owner):...
def __set__(self, instance, value):...
def __delete__(self, instance):...
需要注意的是,__get__ 的返回值需要是屬性值或拋異常,另外兩個(gè)方法要返回None。
還需注意的是不要把__delete__ 和__del__ 搞混了,前者是實(shí)現(xiàn)描述符協(xié)議的一個(gè)方法,后者是對(duì)象銷(xiāo)毀函數(shù)(也常稱(chēng)為析構(gòu)函數(shù))。
先不管這幾個(gè)方法中的參數(shù),看一個(gè)示例先:
class Descriptor():
def __get__(self, instance, owner):
print("self: %s\ninstance: %s\nowner: %s" % (self, instance, owner))
class S:
# 描述符的示例對(duì)象作為S的屬性
attr = Descriptor()
s1 = S()
s1.attr # 訪(fǎng)問(wèn)對(duì)象屬性
print("-" * 30)
S.attr # 訪(fǎng)問(wèn)類(lèi)屬性
輸出結(jié)果:
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'>
不難看出,在訪(fǎng)問(wèn)類(lèi)S中的屬性attr時(shí),表示訪(fǎng)問(wèn)描述符類(lèi)的實(shí)例對(duì)象,它會(huì)自動(dòng)調(diào)用描述符類(lèi)中的__get__ 方法。
在這個(gè)方法中,3個(gè)參數(shù)self、instance、owner分別對(duì)應(yīng)的內(nèi)容從結(jié)果中已經(jīng)顯示出來(lái)了。它們之間有以下等價(jià)關(guān)系:
s1.attr -> Descriptor.__get__(S.attr, s1, S)
S.attr -> Descriptor.__get__(S.attr, None, S)
所以,這里解釋下__get__(self, instance, owner) 中的三個(gè)參數(shù):
- self:描述符對(duì)象自身,也就是被代理類(lèi)S中的屬性attr
- instance:被代理類(lèi)的實(shí)例對(duì)象。所以訪(fǎng)問(wèn)類(lèi)屬性(class.attr)時(shí)為None
- owner:將描述符對(duì)象附加到哪個(gè)類(lèi)上,其實(shí)是instance所屬的類(lèi),也就是type(instance)
再解釋下這里相關(guān)的幾個(gè)角色:
Descriptor :是描述符類(lèi),也是代理者
S :是另一個(gè)類(lèi),是托管類(lèi)、客戶(hù)類(lèi),也就是參數(shù)中的owner
attr = Descriptor() :是描述符的實(shí)例對(duì)象,attr是托管類(lèi)的屬性,也就參數(shù)中的self
s1 :是托管類(lèi)實(shí)例對(duì)象,也就是參數(shù)中的instance
按照descriptor的功能,大概可以用上面的方式去定義各個(gè)角色。當(dāng)然,角色的定義沒(méi)什么限制。
descriptor的作用發(fā)揮在哪
當(dāng)定義了一個(gè)類(lèi)后,可以訪(fǎng)問(wèn)、賦值、刪除它的屬性,這些操作也同樣適用于它的實(shí)例對(duì)象。
例如Foo類(lèi):
class Foo():
...
f = Foo()
a = f.bar # 訪(fǎng)問(wèn)屬性
f.bar = b # 賦值屬性
del f.bar # 刪除屬性
decriptor發(fā)揮作用的時(shí)候就在于執(zhí)行這3類(lèi)操作的時(shí)候:
- 當(dāng)訪(fǎng)問(wèn)x.d的時(shí)候,將自動(dòng)調(diào)用描述符類(lèi)中的
__get__
- 當(dāng)賦值x.d的時(shí)候,將自動(dòng)調(diào)用描述符類(lèi)中的
__set__
- 當(dāng)刪除x.d的時(shí)候,將自動(dòng)調(diào)用描述符類(lèi)中的
__delete__
考慮一下:如果x所屬的類(lèi)中已經(jīng)定義了__getattr__ 、__setattr__ 、__delattr__ 會(huì)如何,是描述符類(lèi)中的先生效,還是x自身所屬類(lèi)的這幾個(gè)方法會(huì)生效。再繼續(xù)考慮,如果x所屬類(lèi)沒(méi)有定義,但它的父類(lèi)定義了這幾個(gè)方法,誰(shuí)會(huì)生效??勺孕袦y(cè)試或者參考我的下一篇文章。
示例1:原始代碼
假設(shè)現(xiàn)在有一個(gè)Student類(lèi),需要記錄stuid、name、score1、score2、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())
但是現(xiàn)在有個(gè)需求,要求score1-score3 的數(shù)值范圍只能是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]")
這個(gè)修改對(duì)于初始化Student對(duì)象時(shí)有效,但Python中屬性的賦值太過(guò)自由,之后可以隨意賦值:
stu = Student("20101120", "malong", 67, 77, 88)
stu.score1 = -23
print(stu.returnMe())
使用property
使用Property或者自定義的getter、setter或運(yùn)算符__getattr__ 、__setattr__ 重載都能解決上面的問(wèn)題,保證無(wú)法賦值超出0到100范圍內(nèi)的數(shù)值。
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)
折疊
下面測(cè)試時(shí)將拋出異常。
stu = Student("20101120", "malong", 67, 77, 88)
print(stu.returnMe())
stu.score1 = -23
但很顯然,上面的重復(fù)代碼太多了。
使用descriptor
如果使用descriptor,將很容易解決上面的問(wèn)題。只需將score1、score2、score3交給描述符類(lèi)托管即可。
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():
# 托管屬性定義在類(lèi)級(jí)別上
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
折疊
很明顯地,它們的代碼被完整地復(fù)用了。這里score1、score2、score3被描述符類(lèi)Score托管了,這3個(gè)分值分別被放進(jìn)了Score實(shí)例對(duì)象的dict中(是單獨(dú)存放它們還是使用dict數(shù)據(jù)結(jié)構(gòu)來(lái)保存,取決于你)。
另外,上面使用了弱引用的字典,因?yàn)槊總€(gè)屬性只在描述符對(duì)象中才會(huì)被用上,為了保證Student對(duì)象被銷(xiāo)毀的時(shí)候能釋放這些資源,所以采用弱引用,避免出現(xiàn)內(nèi)存泄漏。
參考資料
轉(zhuǎn)載請(qǐng)注明出處:https://www.cnblogs.com/f-ck-need-u/p/10198020.html 如果覺(jué)得文章不錯(cuò),不妨給個(gè)打賞,寫(xiě)作不易,各位的支持,能激發(fā)和鼓勵(lì)我更大的寫(xiě)作熱情。謝謝!
|