python——描述符
本文主要介紹描述符的定義,個人的一些理解;什麽是數據描述符;什麽是非數據描述符;描述符的檢測等。希望看完這篇文章後,你對描述符有了更清晰的認識。知道怎麽判斷一個對象是不是描述符,知道如果定義一個描述符,知道什麽是該用描述符。當然,最大的目的是,通過學習描述符,讓你對python這門語言有更多深入的認識。
什麽是描述符
官方的定義:描述符是一種具有“捆綁行為”的對象屬性。訪問(獲取、設置和刪除)它的屬性時,實際是調用特殊的方法(get(),set(),delete())。也就是說,如果一個對象定義了這三種方法的任何一種,它就是一個描述符。
更多的理解:
通常情況下,訪問一個對象的搜索鏈是怎樣的?比如a.x,首先,應該是查詢 a.dict
三個方法(協議):
? get(self, instance, owner) —獲取屬性時調用,返回設置的屬性值,通常是set中的value,或者附加的其他組合值。
? set(self, instance, value) — 設置屬性時調用,返回None.
? delete(self, instance) — 刪除屬性時調用,返回None
其中,instance是這個描述符屬性所在的類的實體,而owner是描述符所在的類。
數據描述符(data descriptor)和非數據描述符(non-data descriptors)
數據描述符:定義了set 和get方法的對象
非數據描述符:只定義了get方法的對象。通常方法都是非數據描述符。比如後面會談到的staticmethod,classmethod等。
區別:
1、位於搜索鏈上的順序。搜索鏈(或者優先鏈)的順序大概是這樣的:數據描述符>實體屬性(存儲在實體的_dict_中)>非數據描述符。
這個順序初看起來挺晦澀。解釋如下:
獲取一個屬性的時候:
首先,看這個屬性是不是一個數據描述符,如果是,就直接執行描述符的get,並返回值。
其次,如果這個屬性不是數據描述符,那就按常規去從dict
裏面取。最後,如果dict裏面還沒有,但這是一個非數據描述符,則執行非數據描述符的get方法,並返回。
舉例如下:
class NoDataDesc(object):
def __get__(self, instance, owner):
print "nodatadesc.attr"
return instance.freq*2
class DataDesc(object):
def __get__(self, instance, owner):
print "datadesc.attr"
return instance.freq*4
def __set__(self, instance, value):
print "set:datadesc.attr"
instance.doc_freq = instance.freq*value
def __delete__(self, instance):
print "del datadesc.attr"
raise BaseException
class Test(object):
data_result = DataDesc()
nodata_result = DataDesc()
def __init__(self,freq,data,nodata):
self.freq = freq
self.data_result = data
self.nodata_result = nodata
test = Test(3,-1,-1)
print test.nodata_result
#print test.data_result12345678910111213141516171819202122232425
註釋print test.data_result的時候打印的是:
set:datadesc.attr
-1(這個值是實體的值,相當於打印test.dict[” nodata_result “])
“set:datadesc.attr”是
self.data_result = data
時打印的。
註釋print test.nodata_result的輸出:
set:datadesc.attr
datadesc.attr
12(這個是描述符set中設置的值。相當於data_result.get(test,3),返回的是12)
談到這個搜索鏈的順序,稍微再引申一下。問一個問題:
如果Test這個類還有getattr,getattribute 呢?
這裏就不多介紹getattr,getattribute (每一個都可以另外再起一篇文章了)。熟悉的記住一點就行:
getattribute優先級是最高的,而getattr是最低的。
如何檢測一個對象是不是描述符
如果把問題換成——一個對象要滿足什麽條件,它才是描述符呢——那是不是回答就非常簡單了?
答:只要定義了(set,get,delete)方法中的任意一種或幾種,它就是個描述符。
那麽,繼續問:怎麽判斷一個對象定義了這三種方法呢?
立馬有人可能就會回答:你是不是傻啊?看一下不就看出來了。。。
問題是,你看不到的時候呢?python內置的staticmethod,classmethod怎麽看?
正確的回答應該是:看這個對象的dict。
寫到這裏,我得先停一下,來解釋一個問題。不知道讀者有沒有發現,上面我一直再說“對象”,“對象”,而實際上指的明明是一個類啊?博主是不是搞錯了呢?嗯,作為一名入門語言是java(拋開只玩過,沒寫過軟件的C)的程序猿,把類稱之為對象,的確讓我本人也有點別扭。但是,在python中,這樣稱呼又是妥當的。因為,“一切皆對象”,類,不過也是元類的一種對象而已。
跑了下題,接著說。要看對象的dict好辦,直接dir(對象)就行了。現在可以寫出檢測對象是不是描述符的方法了:
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
12345678910111213
測試一下:
print is_descriptor(classmethod), is_data_descriptor(classmethod)
print is_descriptor(staticmethod), is_data_descriptor(staticmethod)
print is_data_descriptor(property)123
輸出:
(True, False)
(True, False)
True
看來,特性(property)是數據描述符
描述符有什麽用和好處
1)一般情況下不會用到,建議:先定基本的,以後真有需要再擴展。別貪玩。
2)可以在設置屬性時,做些檢測等方面的處理
3)緩存?
4)設置屬性不能被刪除?那定義delete方法,並raise 異常。
5)還可以設置只讀屬性
6)把一個描述符作為某個對象的屬性。這個屬性要更改,比如增加判斷,或變得更復雜的時候,所有的處理只要在描述符中操作就行了。
老實說,博主也想不出太多描述符使用的場景,舉不出什麽例子來。希望有人能幫忙補充。不甚感激。
例子
1、一個學校類,學校類又一個學生屬性,賦給這個屬性的值是一個json格式的字符串。需要json解析。現在是要處理學校很多學生的數據。要求:不能因為某個學生屬性有問題而導致其他學生的數據有問題。並且這個學生屬性所在這條記錄要保存,對於它的屬性,置空。
代碼如下:
class Student(object):
def __init__(self):
self.inst = {}
def __get__(self, instance, owner):
return self.inst[instance]
def __set__(self, instance, value):
try:
self.inst[instance] = json.loads(value)
except ValueError,e:
print "error"
self.inst[instance] = ""
def __delete__(self, instance):
raise BaseException
class School(object):
student = Student()
def __init__(self,tid,title,student):
self.tid = tid
self.name = title
self.student = student
def main():
lst = []
for i in xrange(10):
student = '{"aturbo":{"english":"100","math":"100"}}'
if i ==1:
student = '{"sansan":{"english":"40","scid":"20"}}'
if i == 3:
#print i
student ='{"sansan"}'
scl = School(str(i),"fege",student)
lst.append(scl)
for info in lst:
print info.tid,':',info.student
if __name__ =="__main__":
main()12345678910111213141516171819202122232425262728293031323334353637383940
打印結果:
0 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
1 : {u’sansan’: {u’scid’: u’20’, u’english’: u’40’}}
2 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
3 :
4 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
5 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
6 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
7 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
8 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
9 : {u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
註意兩點:
1)tid為1這條的取值,tid=3這條的取值
2)這樣定義Student這個描述符也可以:
class Student(object):
def __get__(self, instance, owner):
return instance.value
def __set__(self, instance, value):
try:
instance.value = json.loads(value)
except ValueError,e:
print "error"
instance.value = ""
def __delete__(self, instance):
raise BaseException12345678910111213
這樣呢:
class Student(object):
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
try:
self.value = json.loads(value)
except ValueError, e:
print "error"
self.value = ""12345678910
也正確,但student變成類成員,不會是實體成員,打印的結果會全部一樣(都是:{u’aturbo’: {u’math’: u’100’, u’english’: u’100’}}
)
總結
你可能還從沒有在實際工作中寫過一個描述符,甚至覺得今後也極少有機會會用的。但是,不可避免地,你每天(只要是在寫python代碼)都或多或少的要和描述符打交道。要非常好的理解描述符並不是一件容易的事,希望這篇這篇文章能夠在一定程度上幫到你。相信我,好好理解下描述符會讓你對python的認識提高不少。
python——描述符