1. 程式人生 > >python——描述符

python——描述符

__name__ intersect for in 問題 那是 exceptio section elf per

本文主要介紹描述符的定義,個人的一些理解;什麽是數據描述符;什麽是非數據描述符;描述符的檢測等。希望看完這篇文章後,你對描述符有了更清晰的認識。知道怎麽判斷一個對象是不是描述符,知道如果定義一個描述符,知道什麽是該用描述符。當然,最大的目的是,通過學習描述符,讓你對python這門語言有更多深入的認識。

什麽是描述符

官方的定義:描述符是一種具有“捆綁行為”的對象屬性。訪問(獲取、設置和刪除)它的屬性時,實際是調用特殊的方法(get(),set(),delete())。也就是說,如果一個對象定義了這三種方法的任何一種,它就是一個描述符。
更多的理解
通常情況下,訪問一個對象的搜索鏈是怎樣的?比如a.x,首先,應該是查詢 a.dict

[‘x’],然後是type(a).dict[‘x’],一直向上知道元類那層止(不包括元類)。如果這個屬性是一個描述符呢?那python就會“攔截”這個搜索鏈,取而代之調用描述符方法(get)。
三個方法(協議):
? get(self, instance, owner) —獲取屬性時調用,返回設置的屬性值,通常是set中的value,或者附加的其他組合值。
? set(self, instance, value) — 設置屬性時調用,返回None.
? delete(self, instance) — 刪除屬性時調用,返回None
其中,instance是這個描述符屬性所在的類的實體,而owner是描述符所在的類。

數據描述符(data descriptor)和非數據描述符(non-data descriptors)

數據描述符:定義了setget方法的對象
非數據描述符:只定義了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——描述符