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
,直譯器也不會有任何異常。當然可以想到解決辦法,就是提供一個getter
,setter
方法來統一讀寫name
,讀寫前新增安全驗證邏輯。
class Test(object): name = None @classmethoddef 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
邏輯編寫單獨的類來處理。