數據描述符(class 內置 get/set/del方法 ):
# 什么是描述符 # 官方的定義:描述符是一種具有“捆綁行為”的對象屬性。訪問(獲取、設置和刪除)它的屬性時,實際是調用特殊的方法(_get_(), # _set_(),_delete_())。也就是說,如果一個對象定義了這三種方法的任何一種,它就是一個描述符。 # 更多的理解: # 通常情況下,訪問一個對象的搜索鏈是怎樣的?比如a.x,首先,應該是查詢 a._dict_[‘x’],然后是type(a)._dict_[‘x’],一直 # 向上知道元類那層止(不包括元類)。如果這個屬性是一個描述符呢?那python就會“攔截”這個搜索鏈,取而代之調用描述符方法 # (_get_)。 # 描述符有什么作用? # The default behavior for attribute access is to get, set, or delete the attribute from an object's dictionary. # For instance, a.x has a lookup chain starting witha.__dict__[‘x'], then type(a).__dict__[‘x'], and continuing # through the base classes of type(a) excluding metaclasses. If the looked-up value is an object defining one of # the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. # Where this occurs in the precedence chain depends on which descriptor methods were defined. # —–摘自官方文檔 # 簡單的說描述符會改變一個屬性的基本的獲取、設置和刪除方式。 # 數據描述符(data descriptor)和非數據描述符(non-data descriptors) # 數據描述符:定義了_set_ 和_get_方法的對象 # 非數據描述符:只定義了_get_方法的對象。通常方法都是非數據描述符。 # 區別: # 1、位於搜索鏈上的順序。搜索鏈(或者優先鏈)的順序大概是這樣的:數據描述符>實體屬性(存儲在實體的_dict_中)>非數據描述符。 # 這個順序初看起來挺晦澀。解釋如下: # 獲取一個屬性的時候: # 首先,看這個屬性是不是一個數據描述符,如果是,就直接執行描述符的_get_,並返回值。 # 其次,如果這個屬性不是數據描述符,那就按常規去從_dict_里面取。 # 最后,如果_dict_里面還沒有,但這是一個非數據描述符,則執行非數據描述符的_get_方法,並返回。 # 三個方法(協議): # • _get_(self, instance, owner) —獲取屬性時調用,返回設置的屬性值,通常是_set_中的value,或者附加的其他組合值。 # • _set_(self, instance, value) — 設置屬性時調用,返回None. # • _delete_(self, instance) — 刪除屬性時調用,返回None # 其中,instance是這個描述符屬性所在的類的實體,而owner是描述符所在的類。 # 為什么要區分數據描述符和非數據描述符?(訪問屬性的優先級不一樣) # 1.非數據描述符的獲取屬性的優先級鏈是,__getattribute__->找__dict__->找描述符,這條鏈的規則給了"緩存屬性"理論支持(通常是這種) # 2.數據描述符,又分為: # 2.1 有__get__方法的覆蓋型的獲取屬性的優先級鏈是,__getattribute__->找描述符,同時__set__方法也會覆蓋對實例屬性的賦值 # 操作,就是說任何外部對屬性的賦值都將被__set__捕獲,同時獲取屬性也是通過__get__方法獲取,__dict__不再起直接作用。 # 2.2 沒有__get__方法的覆蓋型的獲取屬性優先級鏈是在沒有對屬性賦值時是__getattribute__->找描述符(返回描述符對象本身) # ,對屬性賦值了之后是__getattribute__->找__dict__->找描述符 # 對象屬性的訪問順序: # ①.實例屬性 # ②.類屬性 # ③.父類屬性 # ④.__getattr__()方法 #數據描述符 - 優先級順序: # ① __getattribute__(), 無條件調用 # ② 數據描述符:由 ① 觸發調用 (若人為的重載了該 __getattribute__() 方法,可能會調職無法調用描述符) # ③ 實例對象的字典(若與描述符對象同名,會被覆蓋哦) # ④ 類的字典 # ⑤ 非數據描述符 # ⑥ 父類的字典 # ⑦ __getattr__() 方法 # 描述符有什么用和好處 # 1)一般情況下不會用到,建議:先定基本的,以后真有需要再擴展。別貪玩。 # 2)可以在設置屬性時,做些檢測等方面的處理 # 3)緩存? # 4)設置屬性不能被刪除?那定義_delete_方法,並raise 異常。 # 5)還可以設置只讀屬性 # 6)把一個描述符作為某個對象的屬性。這個屬性要更改,比如增加判斷,或變得更復雜的時候,所有的處理只要在描述符中操作就行了。 # #舉例: # @property # #這就是一個數據描述符 # class Afff(): # pass # 更詳盡的示例: #摘錄於: #描述符闡述: # https://blog.csdn.net/allenalex/article/details/54097319 #應用示例: # https://www.jb51.net/article/91028.htm #優先級闡述: # https://www.cnblogs.com/Jimmy1988/p/6808237.html
示例:
#描述符類(相當於代理) #定義為了數據描述符:定義了_set_ 和_get_方法的對象 class Foo(): def __get__(self, instance, owner): print("執行Foo get方法") def __set__(self, instance, value): print("執行Foo set方法") def __delete__(self): print("執行Foo del方法") #主要運行的類: class Test(): #類的x屬性被Foo代理,所以屬性訪問優先級也被修改: #類屬性 > 數據描述符 > 實例屬性 > 非實例屬性 > __getattr__() x = Foo() def __init__(self,num): self.x = num #因為x類屬性被Foo代理,觸發Foo的set方法,而set方法只是打印了,沒有做數據操作, abc = Test(100) abc.x print(abc.__dict__) # print輸出:----------- # 執行Foo set方法 # 執行Foo get方法 # {} # 執行Foo del方法 # print輸出:-----------
練習:
#描述符應用- 練習(判斷錄入類型是否合規) class Test(): def __init__(self, key,type_data): self.key = key self.type_data = type_data def __set__(self, instance, value): if isinstance(value,self.type_data): instance.__dict__[self.key] = value else: print("賦值類型錯誤") class People(): name =Test('name',str) def __init__(self,name,old): self.name = name self.old = old #賦值為str類型 name = 'anec' #賦值為int類型 name2 = 123 abc = People(name,23) abc2 = People(name2,24) print(abc.__dict__) print(abc2.__dict__)