Python入門之面向對象編程(四)Python描述器詳解


本文分為如下部分

  • 引言——用@property批量使用的例子來引出描述器的功能
  • 描述器的基本理論及簡單實例
  • 描述器的調用機制
  • 描述器的細節
  • 實例方法、靜態方法和類方法的描述器原理
  • property裝飾器的原理
  • 描述器的應用
  • 參考資料

 

引言

前面python面向對象的文章中我們講到過,我們可以用@property裝飾器將方法包裝成屬性,這樣的屬性,相比於其他屬性有一個優點就是可以在對屬性賦值時,進行變量檢查,舉例代碼如下、

class A:
    def __init__(self, name, score):
        self.name = name # 普通屬性
        self._score = score
        
    @property
    def score(self):
        return self._score
    
    @score.setter
    def score(self, value):
        print('setting score here')
        if isinstance(value, int):
            self._score = value
        else:
            print('please input an int')
        
a = A('Bob',90)
a.name # 'Bob'
a.score # 90
a.name = 1
a.name # 1 ,名字本身不應該允許賦值為數字,但是這里無法控制其賦值
a.score = 83
a.score # 83,當賦值為數值型的時候,可以順利運行
a.score = 'bob' # please input an int
a.score # 83,賦值為字符串時,score沒有被改變

當我們有很多這樣的屬性時,如果每一個都去使用@property,代碼就會過於冗余。如下

class A:
    def __init__(self, name, score, age):
        self.name = name # 普通屬性
        self._score = score
        self._age = age
        
    @property
    def score(self):
        return self._score
    
    @score.setter
    def score(self, value):
        print('setting score here')
        if isinstance(value, int):
            self._score = value
        else:
            print('please input an int')
            
    @property
    def age(self):
        return self._age
    
    @age.setter
    def age(self, value):
        print('setting age here')
        if isinstance(value, int):
            self._age = value
        else:
            print('please input an int')
            
a = A('Bob', 90, 20)

因為每一次檢驗的方法都是一樣的,所以最好有方法可以批量實現這件事,只寫一次if isinstance。描述器就可以用來實現這件事。

為了能夠更清楚地理解描述器如何實現,我們先跳開這個話題,先講一講描述器的基本理論。

 

描述器基本理論及簡單實例

描述器功能強大,應用廣泛,它可以控制我們訪問屬性、方法的行為,是@property、super、靜態方法、類方法、甚至屬性、實例背后的實現機制,是一種比較底層的設計,因此理解起來也會有一些困難。

定義:從描述器的創建來說,一個類中定義了__get____set____delete__中的一個或幾個,這個類的實例就可以叫做一個描述器。

為了能更真切地體會描述器是什么,我們先看一個最簡單的例子,這個例子不實現什么功能,只是使用了描述器

# 創建一個描述器的類,它的實例就是一個描述器
# 這個類要有__get__  __set__ 這樣的方法
# 這種類是當做工具使用的,不單獨使用
class M:
    def __init__(self, x=1):
        self.x = x
        
    def __get__(self, instance, owner):
        return self.x
    
    def __set__(self, instance, value):
        self.x = value
        
# 調用描述器的類
class AA:
    m = M() # m就是一個描述器
    
aa = AA()
aa.m # 1
aa.m = 2
aa.m # 2

我們分析一下上面這個例子

  • 創建aa實例和普通類沒什么區別,我們從aa.m開始看
  • aa.m是aa實例調用了m這個類屬性,然而這個類屬性不是普通的值,而是一個描述器,所以我們從訪問這個類屬性變成了訪問這個描述器
  • 如果調用時得到的是一個描述器,python內部就會自動觸發一套使用機制
  • 訪問的話自動觸發描述器的__get__方法
  • 修改設置的話就自動觸發描述器的__set__方法
  • 這里就是aa.m觸發了__get__方法,得到的是self.x的值,在前面__init__中定義的為1
  • aa.m = 2則觸發了__set__方法,賦的值2傳到value參數之中,改變了self.x的值,所以下一次aa.m調用的值也改變了

進一步思考:當訪問一個屬性時,我們可以不直接給一個值,而是接一個描述器,讓訪問和修改設置時自動調用__get__方法和__set__方法。再在__get__方法和__set__方法中進行某種處理,就可以實現更改操作屬性行為的目的。這就是描述器做的事情。

相信有的讀者已經想到了,開頭引言部分的例子,就是用描述器這樣實現的。在講具體如何實現之前,我們要先了解更多關於描述器的調用機制

 

描述器的調用機制

aa.m命令其實是查找m屬性的過程,程序會先到哪里找,沒有的話再到哪里找,這是有一個順序的,說明訪問順序時需要用到__dict__方法。

先看下面的代碼了解一下__dict__方法

class C:
    x = 1
    def __init__(self, y):
        self.y = y
        
    def fun(self):
        print(self.y)
        
c = C(2)
# 實例有哪些屬性
print(c.__dict__) # {'y': 2}
# 類有什么屬性
print(C.__dict__) # 里面有 x fun
print(type(c).__dict__) # 和上一條一樣

print(vars(c)) # __dict__ 也可以用 vars 函數替代,功能完全相同

# 調用
c.fun() # 2
c.__dict__['y'] # 2
# type(c).__dict__['fun']() # 報錯,說明函數不是這么調用的

__dict__方法返回的是一個字典,類和實例都可以調用,鍵就是類或實例所擁有的屬性、方法,可以用這個字典訪問屬性,但是方法就不能這樣直接訪問,原因我們之后再說。

下面我們來說一下,當我們調用aa.m時的訪問順序

  • 程序會先查找 aa.__dict__['m'] 是否存在
  • 不存在再到type(aa).__dict__['m']中查找
  • 然后找type(aa)的父類
  • 期間找到的是普通值就輸出,如果找到的是一個描述器,則調用__get__方法

下面我們來看一下__get__方法的調用機制

class M:
    def __init__(self):
        self.x = 1
        
    def __get__(self, instance, owner):
        return self.x
    
    def __set__(self, instance, value):
        self.x = value
        
# 調用描述器的類
class AA:
    m = M() # m就是一個描述器
    n = 2
    def __init__(self, score):
        self.score = score
    
        
aa = AA(3)
print(aa.__dict__) # {'score': 3}
print(aa.score) # 3, 在 aa.__dict__ 中尋找,找到了score直接返回
print(aa.__dict__['score']) # 3, 上面的調用機制實際上是這樣的

print(type(aa).__dict__) # 里面有n和m
print(aa.n) # 2, 在aa.__dict__中找不到n,於是到type(aa).__dict__中找到了n,並返回其值
print(type(aa).__dict__['n']) # 2, 其實是上面一條的調用機制

print(aa.m) # 1, 在aa.__dict__中找不到n,於是到type(aa).__dict__中找到了m
# m是一個描述器對象,於是調用__get__方法,將self.x的值返回,即1
print(type(aa).__dict__['m'].__get__(aa,AA)) # 1, 上面一條的調用方式是這樣的
# __get__的定義中,除了self,還有instance和owner,其實分別表示的就是描述器所在的實例和類,這里的細節我們后文會講

print('-'*20)
print(AA.m) # 1, 也是一樣調用了描述器
print(AA.__dict__['m'].__get__(None, AA)) # 類相當於調用這個

此外還有特例,與描述器的種類有關

  • 同時定義了__get____set__方法的描述器稱為資料描述器
  • 只定義了__get__的描述器稱為非資料描述器
  • 二者的區別是:當屬性名和描述器名相同時,在訪問這個同名屬性時,如果是資料描述器就會先訪問描述器,如果是非資料描述器就會先訪問屬性 舉例如下
# 既有__get__又有__set__,是一個資料描述器
class M:
    def __init__(self):
        self.x = 1
        
    def __get__(self, instance, owner):
        print('get m here') # 打印一些信息,看這個方法何時被調用
        return self.x
    
    def __set__(self, instance, value):
        print('set m here') # 打印一些信息,看這個方法何時被調用
        self.x = value + 1 # 這里設置一個+1來更清楚了解調用機制

# 只有__get__是一個非資料描述器
class N:
    def __init__(self):
        self.x = 1
        
    def __get__(self, instance, owner):
        print('get n here') # 打印一些信息,看這個方法何時被調用
        return self.x
        
# 調用描述器的類
class AA:
    m = M() # m就是一個描述器
    n = N()
    def __init__(self, m, n):
        self.m = m # 屬性m和描述器m名字相同,調用時發生一些沖突
        self.n = n # 非資料描述器的情況,與m對比
    
aa = AA(2,5)
print(aa.__dict__) # 只有n沒有m, 因為資料描述器同名時,不會訪問到屬性,會直接訪問描述器,所以屬性里就查不到m這個屬性了
print(AA.__dict__) # m和n都有
print(aa.n) # 5, 非資料描述器同名時調用的是屬性,為傳入的5
print(AA.n) # 1, 如果是類來訪問,就調用的是描述器,返回self.x的值

print(aa.m) # 3, 其實在aa=AA(2,5)創建實例時,進行了屬性賦值,其中相當於進行了aa.m=2
# 但是aa調用m時卻不是常規地調用屬性m,而是資料描述器m
# 所以定義實例aa時,其實觸發了m的__set__方法,將2傳給value,self.x變成3
# aa.m調用時也訪問的是描述器,返回self.x即3的結果
# 其實看打印信息也能看出什么時候調用了__get__和__set__

aa.m = 6 # 另外對屬性賦值也是調用了m的__set__方法
print(aa.m) # 7,調用__get__方法

print('-'*20)
# 在代碼中顯式調用__get__方法
print(AA.__dict__['n'].__get__(None, AA)) # 1
print(AA.__dict__['n'].__get__(aa, AA)) # 1

注:要想制作一個只讀的資料描述器,需要同時定義 __set____get__,並在 __set__ 中引發一個 AttributeError 異常。定義一個引發異常的 __set__ 方法就足夠讓一個描述器成為資料描述器。

 

描述器的細節

本節分為如下兩個部分

  • 調用描述器的原理
  • __get____set__方法中的參數解釋

1.首先是調用描述器的原理 當調用一個屬性,而屬性指向一個描述器時,為什么就會去調用這個描述器呢,其實這是由object.__getattribute__()方法控制的,其中object是新式類定義時默認繼承的類,即py2這么寫的class(object)中的object。新定義的一個類繼承了object類,也就繼承了__getattribute__方法。當訪問一個屬性比如b.x時,會自動調用這個方法 __getattribute__()的定義如下

def __getattribute__(self, key):
    "Emulate type_getattro() in Objects/typeobject.c"
    v = object.__getattribute__(self, key)
    if hasattr(v, '__get__'):
        return v.__get__(None, self)
    return v

上面的定義顯示,如果b.x是一個描述器對象,即能找到__get__方法,則會調用這個get方法,否則就使用普通的屬性。 如果在一個類中重寫__getattribute__,將會改變描述器的行為,甚至將描述器這一功能關閉。

2.__get____set__方法中的參數解釋 官網中標明了這三個方法需要傳入哪些參數,還有這些方法的返回結果是什么,如下所示

descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None 

我們要了解的就是self obj type value分別是什么 看下面一個例子

class M:
    def __init__(self, name):
        self.name = name
        
    def __get__(self, obj, type):
        print('get第一個參數self: ', self.name)
        print('get第二個參數obj: ', obj.age)
        print('get第三個參數type: ', type.name)
        
    def __set__(self, obj, value):
        obj.__dict__[self.name] = value
        
class A:
    name = 'Bob'
    m = M('age')
    def __init__(self, age):
        self.age = age

a = A(20) # age是20
a.m
# get第一個參數self:  age
# get第二個參數obj:  20
# get第三個參數type:  Bob
a.m = 30
a.age # 30

總結如下

  • self是描述器類M中的實例
  • obj是調用描述器的類a中的實例
  • type是調用描述器的類A
  • value是對這個屬性賦值時傳入的值,即上面的30

上面的代碼邏輯如下

  • a.m訪問描述器,調用__get__方法
  • 三次打印分別調用了m.name a.age A.name
  • a.m = 30調用了__set__方法,令a(即obj)的屬性中的'age'(即M('age')這里傳入的self.name)為30

 

實例方法、靜態方法和類方法的描述器原理

本節說明訪問些方法其實都訪問的是描述器,並說明它們調用順序是怎樣的,以及類方法和靜態方法描述器的python定義。

class B:
    @classmethod
    def print_classname(cls):
        print('Bob')
        
    @staticmethod
    def print_staticname():
        print('my name is bob')
        
    def print_name(self):
        print('this name')
        
b = B()
b.print_classname() # 調用類方法
b.print_staticname() # 調用靜態方法
b.print_name() # 調用實例方法
print(B.__dict__) # 里面有實例方法、靜態方法和類方法
# 但其實字典里的還不是可以直接調用的函數
print(B.__dict__['print_classname'])
print(b.print_classname) # 和上不一樣
print(B.__dict__['print_staticname'])
print(b.print_staticname) # 和上不一樣
print(B.__dict__['print_name'])
print(b.print_name) # 和上不一樣

# <classmethod object at 0x0000024A92DA67B8>
# <bound method B.print_classname of <class '__main__.B'>>
# <staticmethod object at 0x0000024A92DA6860>
# <function B.print_staticname at 0x0000024A92D889D8>
# <function B.print_name at 0x0000024A92D88158>
# <bound method B.print_name of <__main__.B object at 0x0000024A92DA6828>>

上面結果表明,實例直接調用時,類方法和實例方法都是bound method,而靜態方法是function。因為靜態方法本身就是定義在類里面的函數,所以不屬於方法范疇。

除此之外,由於實例直接調用后得到的結果可以直接接一個括號,當成函數來調用。而使用字典調用時,得到的結果和實例調用都不一樣,所以它們是不可以直接接括號當成函數使用的。

其實從顯示的結果我們可以看出,靜態方法和類方法用字典調用得到的其實分別是staticmethod和classmethod兩個類的對象,這兩個類其實是定義描述器的類,所以用字典訪問的兩個方法得到的都是描述器對象。它們需要用一個__get__方法才可以在后面接括號當成函數調用。

而普通實例方法用字典調用得到的是一個function即函數,理論上是可以用括號直接調用的,但是調用時報錯說少了self參數,其實它也是描述器對象,用通過__get__方法將self傳入來調用

三種方法本質上調用__get__方法的情況展示如下

B.__dict__['print_classname'].__get__(None, B)()
B.__dict__['print_staticname'].__get__(None, B)()
B.__dict__['print_name'].__get__(b, B)()

print(B.__dict__['print_classname'].__get__(None, B))
print(B.__dict__['print_staticname'].__get__(None, B))

print(B.__dict__['print_name'])
print(B.__dict__['print_name'].__get__(None, B)) # 這是不傳入實例即self的情況,和直接從字典調用結果相同,在python2中是一個unbound method
print(B.__dict__['print_name'].__get__(b, B))

# B.print_name() # 報錯,說少輸入一個self參數
# B.print_name(B()) # this name  輸入實例即不會報錯

所以說我們平常調用的方法都是本質上在調用描述器對象,訪問描述器時自動調用__get__方法。

上面調用時注意到,前兩個__get__的第一個參數都是None,而實例方法是一個b,這是因為實例方法需要具體的實例來調用而不能用類直接調用。在python2中,用類直接調用實例方法得到的是一個unbound method,用實例調用才是一個bound method,(在python3刪除了unbound method的概念,改為function),而類方法本身就可以被類調用,所以參數是None時就是一個bound method了。所以說__get__的第一個參數使用b可以理解成方法的bound過程。

既然三種方法都是調用了描述器對象,那么這些對象都是各自類的實例,它們的類是如何定義的呢?python中這些類的定義是用底層的C語言實現的,為了理解其工作原理,這里展示一個用python語言實現classmethod裝飾器的方法,(來源),即構建能產生類方法對應描述器對象的類。

class myclassmethod(object):
    def __init__(self, method):
        self.method = method
    def __get__(self, instance, cls):
        return lambda *args, **kw: self.method(cls, *args, **kw)
    
class Myclass:
    x = 3
    @myclassmethod
    def method(cls, a):
        print(cls.x+a)
        
m = Myclass()
Myclass.method(a=2)

下面我們分析一下上述代碼

  • 我們看到使用@myclassmethod裝飾器達到的效果和使用@classmethod裝飾器沒有什么區別
  • 首先定義了myclassmethod類,里面使用了__get__方法,所以它的實例會是一個描述器對象
  • myclassmethod當做裝飾器作用於method函數,根據裝飾器的知識,相當於這樣設置method=myclassmethod(method)
  • 調用Myclass.method()時調用了改變后了的method方法,即myclassmethod(method)(a)
  • myclassmethod(method)這是myclassmethod類的一個實例,即一個描述器,此處訪問於是調用__get__方法,返回一個匿名函數
  • __get__中其實是將owner(cls)部分傳入method方法,因為methon在Myclass類中調用,這個owner也就是Myclass類。這一步其實是提前傳入了method的第一個參數cls,后面的參數a由myclassmethod(method)(a)第二個括號調用
  • 仔細分析上面的定義與調用過程,我們會發現,我們常常說的類方法第一個參數要是cls,其實是不對的,第一個參數是任意都可以,它只是占第一個位置,用於接收類實例引用類屬性,隨便換成任意變量都可以,用cls只是約定俗成的。比如下面的代碼正常運行
class Myclass:
    x = 3
    @classmethod
    def method(b, a):
        print(b.x+a)
        
m = Myclass()
Myclass.method(a=2) # 5

下面看一下staticmethod類的等價python定義(來源

class mystaticmethod:
    def __init__(self, callable):
        self.f = callable
    def __get__(self, obj, type=None):
        return self.f
    
class Myclass:
    x = 3
    @mystaticmethod
    def method(a, b):
        print(a + b)
        
m = Myclass()
m.method(a=2, b=3)

注:從源碼角度來理解靜態方法和類方法

  • 靜態方法相當於不自動傳入實例對象作為方法的第一個參數,類方法相當於將默認傳入的第一個參數由實例改為類
  • 使用@classmethod后無論類調用還是實例調用,都會自動轉入類作為第一個參數,不用手動傳入就可以調用類屬性,而沒有@classmethod的需要手動傳入類
  • 既不用@classmethod也不用@staticmethod則類調用時不會自動傳入參數,實例調用時自動傳入實例作為第一個參數
  • 所以說加@classmethod是為了更方便調用類屬性,加@staticmethod是為了防止自動傳入的實例的干擾
  • 除此之外要說明一點:當屬性和方法重名時,調用會自動訪問屬性,是因為這些方法調用的描述器都是非資料描述器。而當我們使用@property裝飾器后,自動調用的就是新定義的get set方法,是因為@property裝飾器是資料描述器

 

property裝飾器的原理

到這里我們可以講一講開頭提出的問題了,即@property裝飾器是如何使用描述器實現的,調用機制是怎樣的,如何通過描述器達到精簡多次使用@property裝飾器的問題。

首先要明確,property有兩種調用形式,一種是用裝飾器,一種是用類似函數的形式,下面會用引言中的例子分別說明兩種形式的調用機制。

下面貼出property的等價python定義(來源於官網的中文翻譯

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

從上面的定義中我們可以看出,定義時分為兩個部分,一個是__get__等方法的定義,另一部分是getter等方法的定義,同時注意到這個類要傳入fget等三個函數作為屬性。getter等方法的定義是為了讓它可以完美地使用裝飾器形式,我們先不看這一部分,先看看不是使用第一種即不使用裝飾器的形式的調用機制。

# 類似函數的形式
class A:
    def __init__(self, name, score):
        self.name = name # 普通屬性
        self.score = score
        
    def getscore(self):
        return self._score
    
    def setscore(self, value):
        print('setting score here')
        if isinstance(value, int):
            self._score = value
        else:
            print('please input an int')
            
    score = property(getscore, setscore)
        
a = A('Bob',90)
a.name # 'Bob'
a.score # 90
a.score = 'bob' # please input an int

分析上述調用score的過程

  • 初始化時即開始訪問score,發現有兩個選項,一個是屬性,另一個是property(getscore, setscore)對象,因為后者中定義了__get____set__方法,因此是一個資料描述器,具有比屬性更高的優先級,所以這里就訪問了描述器
  • 因為初始化時是對屬性進行設置,所以自動調用了描述器的__set__方法
  • __set__中對fset屬性進行檢查,這里即傳入的setscore,不是None,所以調用了fsetsetscore方法,這就實現了設置屬性時使用自定義函數進行檢查的目的
  • __get__也是一樣,查詢score時,調用__get__方法,觸發了getscore方法

下面是另一種使用property的方法

# 裝飾器形式,即引言中的形式
class A:
    def __init__(self, name, score):
        self.name = name # 普通屬性
        self.score = score
        
    @property
    def score(self):
        print('getting score here')
        return self._score
    
    @score.setter
    def score(self, value):
        print('setting score here')
        if isinstance(value, int):
            self._score = value
        else:
            print('please input an int')
        
a = A('Bob',90)
# a.name # 'Bob'
# a.score # 90
# a.score = 'bob' # please input an int

下面進行分析

  • 在第一種使用方法中,是將函數作為傳入property中,所以可以想到是否可以用裝飾器來封裝
  • get部分很簡單,訪問score時,加上裝飾器變成訪問property(score)這個描述器,這個score也作為fget參數傳入__get__中指定調用時的操作
  • 而set部分就不行了,於是有了setter等方法的定義
  • 使用了propertysetter裝飾器的兩個方法的命名都還是score,一般同名的方法后面的會覆蓋前面的,所以調用時調用的是后面的setter裝飾器處理過的score,是以如果兩個裝飾器定義的位置調換,將無法進行屬性賦值操作。
  • 而調用setter裝飾器的score時,面臨一個問題,裝飾器score.setter是什么呢?是scoresetter方法,而score是什么呢,不是下面定義的這個score,因為那個score只相當於參數傳入。自動向其他位置尋找有沒有現成的score,發現了一個,是property修飾過的score,這是個描述器,根據property的定義,里面確實有一個setter方法,返回的是property類傳入fset后的結果,還是一個描述器,這個描述器傳入了fgetfset,這就是最新的score了,以后實例只要調用或修改score,使用的都是這個描述器
  • 如果還有del則裝飾器中的score找到的是setter處理過的score,最新的score就會是三個函數都傳入的score
  • 對最新的score的調用及賦值刪除都跟前面一樣了

property的原理就講到這里,從它的定義我們可以知道它其實就是將我們設置的檢查等函數傳入get set等方法中,讓我們可以自由對屬性進行操作。它是一個框架,讓我們可以方便傳入其他操作,當很多對象都要進行相同操作的話,重復就是難免的。如果想要避免重復,只有自己寫一個類似property的框架,這個框架不是傳入我們希望的操作了,而是就把這些操作放在框架里面,這個框架因為只能實現一種操作而不具有普適性,但是卻能大大減少當前問題代碼重復問題

下面使用描述器定義了Checkint類之后,會發現A類簡潔了非常多

class Checkint:
    
    def __init__(self, name):
        self.name = name
        
    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]
        
    def __set__(self, instance, value):
        if isinstance(value, int):
            instance.__dict__[self.name] = value
        else:
            print('please input an integer')

# 類似函數的形式
class A:
    score = Checkint('score')
    age = Checkint('age')
    
    def __init__(self, name, score, age):
        self.name = name # 普通屬性
        self.score = score
        self.age = age
        
a = A('Bob', 90, 30)
a.name # 'Bob'
a.score # 90
# a.score = 'bob' # please input an int
# a.age='a' # please input an integer

 

描述器的應用

因為我本人也剛剛學描述器不久,對它的應用還不是非常了解,下面只列舉我現在能想到的它有什么用,以后如果想到其他的再補充

  • 首先是上文提到的,它是實例方法、靜態方法、類方法、property的實現原理
  • 當訪問屬性、賦值屬性、刪除屬性,出現冗余操作,或者苦思無法找到答案時,可以求助於描述器
  • 具體使用1:緩存。比如調用一個類的方法要計算比較長的時間,這個結果還會被其他方法反復使用,我們不想每次使用和這個相關的函數都要把這個方法重新運行一遍,於是可以設計出第一次計算后將結果緩存下來,以后調用都使用存下來的結果。只要使用描述器在__get__方法中,在判斷語句下,obj.__dict__[self.name] = value。這樣每次再調用這個方法都會從這個字典中取得值,而不是重新運行這個方法。(例子來源最后的那個例子)

 

參考

 


免責聲明!

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



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