python 属性描述符
生活随笔
收集整理的這篇文章主要介紹了
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. 覆蓋型與非覆蓋型描述符對比
通過實例讀取屬性時, 通常返回的是實例中定義的屬性;
但是,如果實例中沒有指定的屬性, 那么會獲取類屬性。
而為實例中的屬性賦值時,通常會在實例中創建屬性,根本不影響類
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__ 方法 插手接管,但是讀取那個屬性的話,就會直接從實例中返回新賦予的值,而不會返回描述符對象。也就是說,實例屬性會遮蓋描述符,不過 只有讀操作是如此
4.3 非覆蓋型描述符
沒有實現 __set__ 方法的描述符是 非覆蓋 型描述符。
如果設置了同名 的實例屬性,描述符會被遮蓋,致使描述符 無法處理 那個實例的那個屬性
4.4 在類中覆蓋描述符
- 不管 描述符 是不是覆蓋型,為 類屬性 賦值 都能 覆蓋 描述符
5. 描述符用法建議
- 創建只讀屬性最簡單的方式是 使用特性 property
- 使用 描述符類 實現只讀屬性,要記住,__get__ 和 __set__ 兩個方法必須都定義,否則,實例的同名屬性會遮蓋描述符
- 用于 驗證的 描述符 可以 只有 __set__ 方法
- 僅有 __get__ 方法的描述符 可以實現 高效緩存
- 非特殊的方法 可以被 實例屬性遮蓋
總結
以上是生活随笔為你收集整理的python 属性描述符的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java JDK 安装配置
- 下一篇: python web开发 网络编程 TC