七.描述符 __get__, __set__, __delete__
1.什么是描述符
-
描述符的本質就是一個新式類, 在這個新式類中至少實現了
__get__()
,__set__()
,__delete__()
中的一個就稱為描述符, 也被稱為描述符協議__get__(self,inatance,owener)
: 當訪問一個屬性的時候觸發__set__(self,instance,value)
: 為一個屬性賦值時觸發__delete__(self,instance)
: 使用 del 刪除一個屬性的時候觸發
self 描述符的對象 instance 使用描述符的對象 owner 設置了描述符的類(也就是instance的類) value instance的值 -
定義一個描述符
class MyDecriptor:
def __get__(self, instance, owner):
print('觸發get')
def __set__(self, instance, value):
print('觸發set')
def __delete__(self, instance):
print('觸發delete')
🔰以下方法是描述符的實例進行的操作, 並不會觸發上面三種方法,不要搞混了
f1=MyDecriptor()
f1.name='shawn'
print(f1.name)
del f1.name
- 設置簡單代理, 定義成另一個類的類屬性
🌙描述符一
class Str:
def __get__(self, instance, owner):
print('--->觸發 Str_get')
def __set__(self, instance, value):
print('--->觸發 Str_set')
def __delete__(self, instance):
print('--->觸發 Str_Delete')
🌙描述符二
class Int:
def __get__(self, instance, owner):
print('--->觸發 Int_get')
def __set__(self, instance, value):
print('--->觸發 Int_set')
def __delete__(self, instance):
print('--->觸發 Int_Delete')
🌙普通類
class Person:
name = Str() # 將name給Str代理 (也可以說name是描述符對象)
age = Int() # 將age給Int代理 (也可以說age是描述符對象)
def __init__(self, name, age):
self.name = name # 這里賦值就會觸發描述符__set__方法
self.age = age
⭐實例對象
P1 = Person("派大星",22)
# --->觸發 Str_set
# --->觸發 Int_set
⭐查找屬性
print(P1.name)
# --->觸發 Str_get
# None
⭐查找屬性
print(P1.age)
# --->觸發 Int_get
# None
⭐設置屬性
P1.name = "海綿寶寶"
# --->觸發 Str_set
⭐查找屬性
print(P1.name)
# --->觸發 Str_get
# None
- 查看類與對象屬性字典
print(P1.__dict__) # {} 為空
print(Person.__dict__)
'''從中取出了兩個(name,age)
'name': <__main__.Str object at 0x00000267479E7AC8>,
'age': <__main__.Int object at 0x00000267479E7B08>
'''
- 小結
- 只要類屬性被描述符類代理了, 以后使用 [對象] . [屬性], 就會觸發描述符類的__set__, __get__, __delete__方法
- 並且被代理的屬性只在類名稱空間有, 對象名稱空間就沒有該屬性了
2.描述符是做什么的
描述符的作用是用來代理另外一個類的屬性的, 並且必須把描述符定義成這個類的類屬性,不能定義到構造函數中
class Str:
def __get__(self):
pass
class Duck:
name = Str() # 正確 : name被Str代理
def __init__(self):
self.name = Str() # 錯誤 : 只能代理類屬性,不能代理對象屬性
- 代理屬性小示例 : 代理屬性, 並限制傳入數據的類型
🌙數據描述符
class Str:
def __init__(self, agencyName, agencyType):
'''
:param agencyName: 被代理的屬性名字
:param agencyType: 被代理的屬性類型
'''
self.name = agencyName
self.type = agencyType
# 將屬性存在描述符的 __dict__ 里面
self.date_dict = self.__dict__
def __get__(self, instance, owner):
return self.date_dict[instance]
def __set__(self, instance, value):
# 在這里可以進行屬性類型的判斷
if isinstance(value, self.type):
self.date_dict[instance] = value
else:
print("類型不正確")
def __delete__(self, instance):
del self.date_dict[instance]
🌙普通類
class Panda:
name = Str("name", str) # 將描述符實例當做屬性保存
age = Str("age", int)
def __init__(self, name, age):
self.name = name
self.age = age
P1 = Panda("派大星", 18)
print(P1.__dict__) # {} 空的,因為全被代理了
print(P1.name) # 派大星
print(P1.age) # 18
P1.name = "海綿寶寶"
P1.age = 22
print(P1.name) # 海綿寶寶
print(P1.age) # 22
print(P1.__dict__) # {}
# 類型錯誤時
P1.name = 2222 # 類型不正確
P1.age = "3333" # 類型不正確
print(Panda.__dict__["name"].__dict__)
# {'name': 'name', 'type': <class 'str'>, 'date_dict': {...}, <__main__.Panda object at 0x000001DBE96F7A88>: '海綿寶寶'}
由上面示例我們可以知道, 一個類中被代理的屬性將會保存在代理類(也就是描述符)的屬性字典中, 而描述符對象又被當做屬性保存在類的屬性字典中
3.描述符的種類與屬性查找優先級
-
通常的只要定義了
__get__
和 另一個或兩個方法的類, 我們就稱之為數據描述符 -
如果一個類只定義了
__get__
方法 (沒有修改數據的功能) , 我們稱之為非數據描述符 -
屬性查找優先級 :
- 無論有沒有都會先觸發
__getattribute__
- 類屬性
- 數據描述符 (如果數據描述符中重寫了
__getattribute__
可能會導致無法調用描述符) - 實例屬性 (對象屬性)
- 非數據描述符
- 找不到觸發
__getattr__()
的執行
- 無論有沒有都會先觸發
-
示例 : 類屬性 > 數據描述符
🌙數據描述符
class Str:
def __get__(self, instance, owner):
print('--->觸發 Str_get')
def __set__(self, instance, value):
print('--->觸發 Str_set')
def __delete__(self, instance):
print('--->觸發 Str_Delete')
🌙普通類
class Person:
name = Str()
def __init__(self,name):
self.name = name
Person.name = "派大星" # 類的屬性
p = Person("name") # --->觸發 Str_set
print(p.name) # 派大星
🔰由上面的實驗可知, 最先找的是類屬性
- 示例 : 數據描述符 > 實例屬性
🌙數據描述符
class Str:
def __get__(self, instance, owner):
print('--->觸發 Str_get')
def __set__(self, instance, value):
print('--->觸發 Str_set')
def __delete__(self, instance):
print('--->觸發 Str_Delete')
🌙普通類
class Person:
name = Str() # name是描述符對象
def __init__(self,name,age):
self.name = name
self.age = age
P1 = Person("海綿哥哥",99) # --->觸發 Str_set
P1.name = "海綿爸爸" # --->觸發 Str_set
P1.name # --->觸發 Str_get
del P1.name # --->觸發 Str_Delete
print(P1.__dict__) # {'age': 99} (查看對象屬性字典,沒有name屬性)
print(Person.__dict__) # ['name'] (查看類屬性字典,存在name,這個name就是描述符對象)
- 示例 : 示例屬性 > 非數據描述符
🔰以下比較並沒有實際意義, 只是便於對描述符的理解
🌙非數據描述符
class Str:
def __get__(self, instance, owner):
print('--->觸發 Str_get')
def __delete__(self, instance):
print('--->觸發 Str_Delete')
🌙普通類
class Person:
name = Str() # name是描述符對象
def __init__(self,name,age):
self.name = name
self.age = age
P1 = Person("海綿媽媽",88) # 拋出異常 : "AttributeError" 屬性中沒有__set__方法