日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python 属性描述符

發布時間:2024/7/5 python 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python 属性描述符 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 1. 描述符示例:驗證屬性
    • 2. 自動獲取儲存屬性的名稱
    • 3. 繼承改進
    • 4. 覆蓋型與非覆蓋型描述符對比
      • 4.1 覆蓋型描述符
      • 4.2 沒有 `__get__` 方法的覆蓋型描述符
      • 4.3 非覆蓋型描述符
      • 4.4 在類中覆蓋描述符
    • 5. 描述符用法建議

learn from 《流暢的python》

1. 描述符示例:驗證屬性

  • 描述符是對多個屬性 運用 相同存取邏輯的一種方式
  • 描述符是實現了 特定協議 的類,這個協議包括 __get__、__set__ 和 __delete__ 方法
    property 類實現了完整的描述符協議

實現了 __get__、__set__ 或 __delete__ 方法的類是描述符。描述符 的用法是,創建一個實例作為另一個類的類屬性

class Quantity:def __init__(self, storage_name):self.storage_name = storage_name# storage_name 屬性,# 這是托管實例中存儲值的 屬性的名稱def __set__(self, instance, value):# self 是描述符 實例(即 LineItem.weight 或 LineItem.price)# instance 是 托管實例(LineItem 實例)if value > 0:instance.__dict__[self.storage_name] = value# 必須直接 處理托管實例的 __dict__ 屬性;# 如果使用內置的 setattr 函數,# 會再次觸發 __set__ 方法,導致無限遞歸# self.__dict__[self.storage_name] = value 錯誤寫法#else:raise ValueError("value must be greater than 0")class LineItem:weight = Quantity('weight')# 描述符實例綁定給 weight 屬性, 類屬性,所有 LineItem實例共享price = Quantity('price')def __init__(self, description, weight, price):self.description = descriptionself.weight = weightself.price = pricedef subtotal(self):return self.weight * self.price

為了避免 price = Quantity('weight') 這樣的錯誤:
采用如下改進:

2. 自動獲取儲存屬性的名稱

class Quantity:__counter = 0 # Quantity 類屬性,統計實例數量def __init__(self):cls = self.__class__ # cls 是 Quantity 類的引用prefix = cls.__name__ # 'Quantity'index = cls.__counterself.storage_name = "_{}#{}".format(prefix, index)# 每個描述符的 名稱是獨一無二的,# 這種非法命名(#) ,內置的 getattr 和 setattr 可以接受cls.__counter += 1# storage_name 屬性,# 這是托管實例中存儲值的 屬性的名稱def __set__(self, instance, value):# self 是描述符 實例(即 LineItem.weight 或 LineItem.price)# instance 是 托管實例(LineItem 實例)if value > 0:setattr(instance, self.storage_name, value)else:raise ValueError("value must be greater than 0")def __get__(self, instance, owner):# owner 參數是托管類(如 LineItem)的引用,# 通過描述符從托管類中獲取屬性時用得到return getattr(instance, self.storage_name)class LineItem:weight = Quantity() # '_Quantity#0'# 描述符實例綁定給 weight 屬性, 類屬性,所有 LineItem實例共享price = Quantity() # '_Quantity#1'def __init__(self, description, weight, price):self.description = descriptionself.weight = weightself.price = pricedef subtotal(self):return self.weight * self.pricecoconuts = LineItem('Brazilian coconut', 20, 17.95) print(coconuts.weight, coconuts.price) # 20, 17.95 print(getattr(coconuts, '_Quantity#0'), getattr(coconuts, '_Quantity#1')) # 20, 17.95 print(LineItem.weight) # 這種調用方式 # 描述符的 __get__ 方法接收到的 instance 參數值是 None # AttributeError: 'NoneType' object has no attribute '_Quantity#0'

但是上面的報錯信息,讓人困惑,如何修改,最好讓 __get__ 方法返回描述符實例

def __get__(self, instance, owner):if instance is None:return selfreturn getattr(instance, self.storage_name)print(LineItem.weight) # <__main__.Quantity object at 0x000001DCA2683D00>

3. 繼承改進

import abcclass AutoStorge:__counter = 0 # 統計實例數量def __init__(self):cls = self.__class__ # cls 是 Quantity 類的引用prefix = cls.__name__ # NonBlank, Quantityindex = cls.__counterself.storage_name = "_{}#{}".format(prefix, index)# 每個描述符的 名稱是獨一無二的,# 這種非法命名(#) ,內置的 getattr 和 setattr 可以接受cls.__counter += 1# storage_name 屬性,# 這是托管實例中存儲值的 屬性的名稱def __set__(self, instance, value):# self 是描述符 實例(即 LineItem.weight 或 LineItem.price)# instance 是 托管實例(LineItem 實例)setattr(instance, self.storage_name, value) # 沒有驗證,交給Validateddef __get__(self, instance, owner):if instance is None:return selfreturn getattr(instance, self.storage_name)class Validated(abc.ABC, AutoStorge):def __set__(self, instance, value):value = self.validate(instance, value)# __set__ 方法把驗證操作委托給 validate 方法super().__set__(instance, value)# 然后把返回的 value 傳給超類的 __set__ 方法,存儲值@abc.abstractmethod # 在這個類中,validate 是抽象方法def validate(self, instance, value):"""to do"""class Quantity(Validated): # 繼承自 Validated 類def validate(self, instance, value): # 檢查非正數if value <= 0:raise ValueError("value should be positive")return valueclass NonBlank(Validated): # 繼承自 Validated 類"""a string with at least one non-space character"""def validate(self, instance, value):value = value.strip() # 去除首尾空白if len(value) == 0:raise ValueError('value cannot be empty or blank')return valueclass LineItem:description = NonBlank() # '_NonBlank#0'weight = Quantity() # '_Quantity#0'# 描述符實例綁定給 weight 屬性, 類屬性,所有 LineItem實例共享price = Quantity() # '_Quantity#1'def __init__(self, description, weight, price):self.description = descriptionself.weight = weightself.price = pricedef subtotal(self):return self.weight * self.pricecoconuts = LineItem(' Brazilian coconut ', 20, 17.95) print(coconuts.weight, coconuts.price) # 20 17.95 print(getattr(coconuts, '_NonBlank#0')) #Brazilian coconut print(getattr(coconuts, '_Quantity#0'), getattr(coconuts, '_Quantity#1')) # 20 17.95 print(LineItem.weight) # <__main__.Quantity object at 0x0000012B2A9AF9A0>
  • 描述符的典型用途——管理 數據屬性
    這種描述符也叫覆蓋型描述符,因為描述符的 __set__ 方法使用托管實例中的同名屬性覆蓋(即插手接管)了要設置的屬性

4. 覆蓋型與非覆蓋型描述符對比

通過實例讀取屬性時, 通常返回的是實例中定義的屬性;
但是,如果實例中沒有指定的屬性, 那么會獲取類屬性。
而為實例中的屬性賦值時,通常會在實例中創建屬性,根本不影響類

### 輔助函數,僅用于顯示 ### # def cls_name(obj_or_cls):cls = type(obj_or_cls)if cls is type:cls = obj_or_clsreturn cls.__name__.split('.')[-1]def display(obj):cls = type(obj)if cls is type:return '<class {}>'.format(obj.__name__)elif cls in [type(None), int]:return repr(obj)else:return '<{} object>'.format(cls_name(obj))def print_args(name, *args):pseudo_args = ', '.join(display(x) for x in args)print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args))class Overriding:"""也稱數據描述符或強制描述符"""def __get__(self, instance, owner):print_args('get', self, instance, owner)def __set__(self, instance, value):print_args('set', self, instance, value)class OverridingNoGet:"""沒有``__get__``方法的覆蓋型描述符"""def __set__(self, instance, value):print_args('set', self, instance, value)class NonOverriding:"""也稱非數據描述符 或 非遮蓋型描述符"""def __get__(self, instance, owner):print_args('get', self, instance, owner)class Managed:over = Overriding()over_no_get = OverridingNoGet()non_over = NonOverriding()def spam(self):print('-> Managed.spam({})'.format(display(self)))

4.1 覆蓋型描述符

obj = Managed() obj.over # obj.over 觸發描述符的 __get__ 方法, 第二個參數的值是托管實例 obj # -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>) Managed.over # Managed.over 觸發描述符的 __get__ 方法,第二個參數 (instance)的值是 None # -> Overriding.__get__(<Overriding object>, None, <class Managed>) obj.over = 7 # 為 obj.over 賦值,觸發描述符的 __set__ 方法,最后一個參數的 值是 7 # -> Overriding.__set__(<Overriding object>, <Managed object>, 7) obj.over # 會觸發描述符的 __get__ 方法 # -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>) obj.__dict__['over'] = 8 # 跳過描述符,直接通過 obj.__dict__ 屬性設值 print(vars(obj)) # {'over': 8} # 確認值在 obj.__dict__ 屬性中,在 over 鍵名下 obj.over # 然而,即使是名為 over 的實例屬性,Managed.over 描述符仍會覆蓋讀取 obj.over 這個操作 # -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)

4.2 沒有 __get__ 方法的覆蓋型描述符

  • 只實現 __set__ 方法,只有 寫操作由描述符處理。
  • 通過實例讀取描述符會返回 描述符對象本身因為沒有處理讀操作的 __get__ 方法。
  • 如果直接通過實例的 __dict__ 屬性創建同名實例屬性,以后再設置那個屬性時,仍會由 __set__ 方法 插手接管,但是讀取那個屬性的話,就會直接從實例中返回新賦予的值,而不會返回描述符對象。也就是說,實例屬性會遮蓋描述符,不過 只有讀操作是如此
# 這個覆蓋型描述符沒有 __get__ 方法,因此,obj.over_no_get 從 類 中獲取描述符實例 print(obj.over_no_get) # <__main__.OverridingNoGet object at 0x000001BC57E3FA90> # 直接從托管類中讀取描述符實例也是如此 print(Managed.over_no_get) # <__main__.OverridingNoGet object at 0x000001BC57E3FA90> # obj.over_no_get 賦值會觸發描述符的 __set__ 方法 obj.over_no_get = 7 # -> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7) print(obj.over_no_get) # <__main__.OverridingNoGet object at 0x000001BC57E3FA90> # 通過實例的 __dict__ 屬性設置名為 over_no_get 的實例屬性 obj.__dict__['over_no_get'] = 9 # 現在,over_no_get 實例屬性會遮蓋描述符,但是只有 讀 操作是如此 print(obj.over_no_get) # 9 # 為 obj.over_no_get 賦值,仍然經過描述符的 __set__ 方法處理 obj.over_no_get = 7 # -> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7) # 但是讀取時,只要有同名的實例屬性,描述符就會被遮蓋 print(obj.over_no_get) # 9

4.3 非覆蓋型描述符

沒有實現 __set__ 方法的描述符是 非覆蓋 型描述符。
如果設置了同名 的實例屬性,描述符會被遮蓋,致使描述符 無法處理 那個實例的那個屬性

# obj.non_over 觸發描述符的 __get__ 方法,第二個參數的值是 obj obj.non_over # -> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>) # Managed.non_over 是非覆蓋型描述符,因此沒有干涉賦值操作的 __set__ 方法 obj.non_over = 7 print(obj.non_over) # 7 現在,obj 有個名為 non_over 的實例屬性,把 Managed 類的同名 描述符屬性遮蓋掉 Managed.non_over # Managed.non_over 描述符依然存在,會通過 類 截獲這次訪問 # -> NonOverriding.__get__(<NonOverriding object>, None, <class Managed>) del obj.non_over # 如果把 non_over 實例 屬性刪除了 obj.non_over # 讀取 obj.non_over 時,會觸發類中描述符的 __get__ 方法;但要注意,第二個參數的值是托管實例 # -> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)

4.4 在類中覆蓋描述符

  • 不管 描述符 是不是覆蓋型,為 類屬性 賦值 都能 覆蓋 描述符
obj = Managed() Managed.over = 1 Managed.over_no_get = 2 Managed.non_over = 3 print(obj.over, obj.over_no_get, obj.non_over) # 1 2 3

5. 描述符用法建議

  • 創建只讀屬性最簡單的方式是 使用特性 property
  • 使用 描述符類 實現只讀屬性,要記住,__get__ 和 __set__ 兩個方法必須都定義,否則,實例的同名屬性會遮蓋描述符
  • 用于 驗證的 描述符 可以 只有 __set__ 方法
  • 僅有 __get__ 方法的描述符 可以實現 高效緩存
  • 非特殊的方法 可以被 實例屬性遮蓋

總結

以上是生活随笔為你收集整理的python 属性描述符的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。