1. 程式人生 > >python學習筆記6.5-類中描述符的使用

python學習筆記6.5-類中描述符的使用

描述符(Descriptor)就是以特殊方法get(), set(), delete()的形式實現了三個核心的屬性訪問操作(set,get,delete)的類。這些方法接受類例項作為輸入來工作。之後,底層的例項字典會根據需要適當的進行調整。
要使用一個描述符,首先要建立一個描述符類,然後把描述符的例項放在類的定義中作為類變數來使用。事例如下:

#Descriptor attribute for an integer TYPE-checked attribute
class Integer:
    def __init__(self,name):
        self.name = name

    def
__get__(self,instance,cls):
if instance is None: return self else: return instance.__dict__[self.name] def __set__(self,instance,value): if not isinstance(value,int): raise TypeError('Expected an int') instance.__dict__[self.name] = value def
__delete__(self,instance):
del instance.__dict__[self.name] class Point: x = Integer('x') #must be a class variable y = Integer('y') def __init__(self,x,y): self.x = x self.y = y p = Point(2,3) print(p.x) print(p.y) p.x = 5 print(p.x) p.x = 'r' print(p.x) 列印輸出: File "D:/home/WX/test_descriptor.py"
, line 33, in <module> 2 3 5 Traceback (most recent call last): p.x = 'r' File "D:/home/WX/test_descriptor.py", line 14, in __set__ raise TypeError('Expected an int') TypeError: Expected an int

每一個描述符方法都會接受被操作的例項作為輸入。要執行所請求的操作,底層的例項字典(即dict屬性)會根據需要進行適當的調整。描述符的self.name屬性會儲存字典的鍵值,通過這些鍵可以找到儲存在例項字典中的例項資料。(這就是python描述符執行機制,不好理解,但一定要多讀去記住,很快就會理解)

對於大多數python類的特性,描述符都提供了底層的魔法,包括@classmethod、
@staticmethod、@property 甚至是slot()。

通過定義一個描述符,我們可以在很底層的情況下捕獲關鍵的例項操作(get,set,delete),並且可以完全自定義這些操作行為。靈活運用操作符,會讓程式變得更加簡潔易懂。
關於操作符,我們必須有正確的理解,它們必須在類的層次上定義,不能根據例項來產生(很重要)。下面的程式碼無法工作:

class Point:
    def __init__(self,x,y):
        self.x = Integer('x')
        self.y = Integer('y')

另外,在我們的例子中看到,get()方法實現也複雜一些,因為例項變數和類變數是有區別的。如果以類變數的形式訪問描述符,引數instance應該設為None。
這種情況下,標準的做法就是簡單的返回描述符例項本身。
描述符常常作為一個元件出現在大型的程式設計框架中,其中還會涉及裝飾器或者元類。正因為如此,對於描述符的使用可能隱藏很深,幾乎看不到痕跡。例如:

#descriptor for a type_checked attribute
class Typed:
    def __init__(self,name,expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self,instance,value):
        if not isinstance(value,self.expected_type):
            raise TypeError('Expected' + str(self.expected_type))
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

#Class decorator that applies it to selected attribute
def typeassert(**kwargs):
    def decorate(cls):
        for name,expected_type in kwargs.items():
            #Attach a typed descripor to the class
            setattr(cls,name,Typed(name,expected_type))
        return cls
    return decorate
#Example use
@typeassert(name=str, shares=int, price=float)
class Stock:
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price

a = Stock('libai',5,5.4)
a.name = 3

列印輸出:
Traceback (most recent call last):
  File "D:/home/WX/test_descriptor.py", line 61, in <module>
    a.name = 3
  File "D:/home/WX/test_descriptor.py", line 38, in __set__
    raise TypeError('Expected' + str(self.expected_type))
TypeError: Expected<class 'str'>

最後,應該強調的是:如果只想訪問某個特定的類中的一種屬性,並且做一些自定義處理,那麼最好不要編寫描述符來實現。對於這樣的任務,使用@property函式更加簡單。針對於大量重用的程式碼的情況下,使用描述符更加有用(例如,我們需要在自己的程式碼中大量使用描述符提供的功能,或者將其作為庫來使用)