Python中的屬性描述符

導語:本文章記錄了本人在學習Python基礎之元編程篇的重點知識及我的心得,打算入門Python的朋友們能夠來一塊兒學習並交流。

本文重點:python

一、瞭解描述符的定義,功能,協議和用法;
二、瞭解覆蓋型描述符和非覆蓋型描述符的概念和區別;
三、瞭解描述符的用法建議。

1、描述符

描述符:是實現了特定協議的類。
描述符功能:是對多個屬性運用相同存取邏輯的一種方式。
描述符協議:包括__get__、__set__、和__delete__方法。一般只實現部分協議。大多數描述符只實現了__get__和__set__方法,可是property類實現了完整的描述符協議。編程

下面咱們用描述符來實現Python中的動態屬性和特性中說起的訂單結算代碼:segmentfault

第四版:使用描述符實現訂單結算功能緩存

class Quantity:#描述符基於協議實現,無需建立子類。

    def __init__(self,storage_name):
        self.storage_name=storage_name#storage_name是託管實例中存儲值的屬性的名稱。

    def __set__(self, instance, value):#重要!instance是LineItem實例,self是描述符實例。
        if value > 0:
            instance.__dict__[self.storage_name]=value#此處必須直接存入__dict__,不然使用setattr函數會致使無限遞歸。
        else:
            raise ValueError('Value must be > 0')
class LineItem:
    weight = Quantity('weight')#將描述符實例綁定到weight屬性。
    price = Quantity('price')#同上。

    def __init__(self,description,weight,price):
        self.description=description
        self.weight=weight
        self.price=price

    def subtotal(self):
        return self.weight*self.price

小結:描述符類的實例能用做託管類的屬性,這一點很重要!
不過在上文中的託管類定義體中,實例化描述符若是能按照weight = Quantity()這種格式聲明就更好了。爲此咱們須要寫一版自動獲取存取屬性名稱的代碼。函數

第五版:改進描述符類——自動獲取存取屬性名稱工具

class Quantity:#改進版描述符類
    __counter = 0 

    def __init__(self):
        cls = self.__class__ 
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix, index) #每一個描述符實例的屬性名稱都是獨一無二的。
        cls.__counter += 1 

    def __get__(self, instance, owner): #此處owner參數是託管類LineItem。
        return getattr(instance, self.storage_name) #從instance中獲取儲存屬性的值。

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self.storage_name, value) #使用setattr把值儲存在instance中。
        else:
            raise ValueError('value must be > 0')

class LineItem:#託管類
    weight = Quantity()
    price = Quantity()

    def __init__(self,description,weight,price):
        self.description=description
        self.weight=weight
        self.price=price

    def subtotal(self):
        return self.weight*self.price

Tips:一般,咱們不會在使用描述符的模塊中定義描述符,而是在一個單獨的實用工具模塊中定義,以便在整個應用中使用。學習

2、特性工廠函數與描述符類比較

咱們在Python中的動態屬性和特性中提到過,抽象定義特性的方式有兩種,一是使用特性工廠函數,二是使用描述符類。
如今來對兩種方式的優勢進行對比辨析:code

  • 特性工廠函數:模式簡單。
  • 描述符類:模式可拓展。可經過子類共享代碼,構建具備部分相同功能的專用描述符,應用更普遍。

我的建議當兩種模式均能實現目標時,推薦使用描述類。orm

3、覆蓋型描述符與非覆蓋型描述符對比

覆蓋型描述符:實現__set__方法的描述符屬於覆蓋型描述符。
特性是覆蓋型描述符。
非覆蓋型描述符:沒有實現__set__方法的描述符屬於非覆蓋型描述符。
類中定義的方法是非覆蓋型描述符。
小結:若是設置了同名實例屬性,對於非覆蓋型描述符而言會被覆蓋;對於沒有實現__get__方法的覆蓋型描述符而言,在讀操做時描述符對象也會被覆蓋。對象

4、描述符用法建議

  • 使用特性以保持簡單:建立只讀屬性最簡單的方式是使用特性。
  • 只讀描述符必須有__set__方法:只讀描述符必須定義__get__和__set__兩個方法,只讀屬性的__set__方法只需拋出AttributeError異常,並提供合適的錯誤信息。
  • 用於驗證的描述符能夠只有__set__方法:描述符用於驗證屬性時能夠不實現__get__,這樣從實例中讀取同名屬性的速度很快。
  • 僅有__get__方法的描述符能夠實現高效緩存。
  • 非特殊的方法能夠被實例屬性覆蓋。
相關文章
相關標籤/搜索