1. 程式人生 > 實用技巧 >Python中的描述符

Python中的描述符

一、什麼是描述符?

  簡單的說,首先要有一個實現了__get__()、__set__()、__delete__()中任意一種方法的新式類(Python 2.x版本預設舊式類,通過繼承object為新式類),並且這個新式類的例項物件是另外一個類的屬性,這個屬性就被稱之為描述符。

class MyDescriptor:
    def __get__(self, instance, owner):
        print('get called')
        return 'get'

    def __set__(self, instance, value):
        print
('set called') def __delete__(self, instance): print('delete called') class Foo: attr = MyDescriptor() # 描述符

上面程式碼中Foo的屬性attr是類MyDescriptor的例項化物件,MyDescriptor實現了__get__()__set__()__delete__(),那麼attr就是成為了描述符,注意attr必須用類屬性形式寫。

二、描述符有什麼用?

  1.型別檢查

  由於Python是一個動態型別解釋性語言,不像C/C++等靜態編譯型語言,資料型別在編譯時便可以進行驗證,而Python

中必須新增額外的型別檢查邏輯程式碼才能做到這一點,這就是描述符的初衷。比如,有一個測試類Test,其具有一個類屬性name。

class Test(object):
    name = None

​   正常情況下,name的值(其實應該是物件,name是引用)都應該是字串,但是因為Python是動態型別語言,即使執行Test.name = 3,直譯器也不會有任何異常。當然可以想到解決辦法,就是提供一個gettersetter方法來統一讀寫name,讀寫前新增安全驗證邏輯。

class Test(object):
    name = None

    @classmethod
    
def get_name(cls): return cls.name @classmethod def set_name(cls, val): if isinstance(val, str): cls.name = val else: raise TypeError("Must be an string")

雖然以上程式碼勉強可以實現對屬性賦值的型別檢查,但是會導致型別定義的臃腫和邏輯的混亂,而描述符就恰好能解決這一問題。

name屬性定義一個(資料)描述符類,其實現了__get____set__方法,程式碼如下:

class NameDes(object):
    def __init__(self):
        self.__name = None
    def  __get__(self, instance, owner):
        print('call __get__')
        return self.__name
    def  __set__(self, instance, value):
        print('call __set__')
        if  isinstance(value,str):
            self.__name = value
        else:
            raise TypeError("Must be an string")

當例項物件訪問name屬性時,就會呼叫__get__方法,列印name屬性所需要的值,當使用例項物件對name屬性進行修改或賦值時,則會呼叫__set__方法,這時只需要在set方法新增所需限制條件,就可以限制name屬性的型別,彌補了python動態型別的缺陷。

  2.彌補property屬性的缺陷

  property屬性和描述符有一個相似點,那就是都可以對屬性進行限制操作(不瞭解的建議去補下property屬性)

  程式碼如下:

class Student(object):

    def __init__(self, hight):
        self._hight = hight  # 單位cm

    @property
    def hight(self):
        return self._hight

    @hight.setter
    def hight(self, value):
        # 判斷輸入的型別
        if not isinstance(value, int):
            raise TypeError
        # 判斷是否符合邏輯
        if value >= 300 or value < 0:
            raise ValueError
        # 進行修改
        self._hight = value

    @hight.deleter
    def hight(self):
        del self._hight


s01 = Student(175)

print(s01.hight)    # 175

s01.hight = 255
print(s01.hight)    # 255

s01.hight = "hello" # TypeError 型別錯誤

del s01.hight
print(s01.hight)

  通過上述程式碼可以看出property屬性也可以限制屬性的修改賦值,但也發現,s01.hight看似呼叫的是hight這一例項屬性,卻是隱藏呼叫的getter/setter方法,看起來很臃腫。

  對property來說,最大的缺點就是它們不能重複使用。

  如果只是對一個屬性進行限制的話,那麼property屬性用起來也沒有多大問題,但如果同時需要限制多個屬性時,就需要多組getter/setter/deleter方法來實現,極大的降低了程式碼的複用性。

  這就是描述符所解決的問題。描述符是property的升級版,允許你為重複的property邏輯編寫單獨的類來處理。