七.描述符 __get__, __set__, __delete__


七.描述符 __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__ 方法 (沒有修改數據的功能) , 我們稱之為非數據描述符

  • 屬性查找優先級 :

    1. 無論有沒有都會先觸發 __getattribute__
    2. 類屬性
    3. 數據描述符 (如果數據描述符中重寫了__getattribute__可能會導致無法調用描述符)
    4. 實例屬性 (對象屬性)
    5. 非數據描述符
    6. 找不到觸發 __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__方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM