90% 的 Python 開發者不知道的描述符應用

通過上面的講解,咱們已經知道如何定義描述符,且明白了描述符是如何工做的。python

正常人所見過的描述符的用法就是上篇文章提到的那些,我想說的是那只是描述符協議最多見的應用之一,或許你還不知道,其實有不少 Python 的特性的底層實現機制都是基於 描述符協議 的,好比咱們熟悉的@property@classmethod@staticmethodsuper 等。微信

如何實現property

先來講說 property 吧。函數

有了第一篇的基礎,咱們知道了 property 的基本用法。這裏我直接切入主題,從第一篇的例子裏精簡了一下。code

class Student:
    def __init__(self, name):
        self.name = name

    @property
    def math(self):
        return self._math

    @math.setter
    def math(self, value):
        if 0 <= value <= 100:
            self._math = value
        else:
            raise ValueError("Valid value must be in [0, 100]")

不防再簡單回顧一下它的用法,經過property裝飾的函數,如例子中的 math 會變成 Student 實例的屬性。而對 math 屬性賦值會進入 使用 math.setter 裝飾函數的邏輯代碼塊。blog

爲何說 property 底層是基於描述符協議的呢?經過 PyCharm 點擊進入 property 的源碼,很惋惜,只是一份相似文檔同樣的僞源碼,並無其具體的實現邏輯。文檔

不過,從這份僞源碼的魔法函數結構組成,能夠大致知道其實現邏輯。get

這裏我本身經過模仿其函數結構,結合「描述符協議」來本身實現類 property 特性。源碼

代碼以下:it

class TestProperty(object):

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc

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

    def __set__(self, obj, value):
        print("in __set__")
        if self.fset is None:
            raise AttributeError
        self.fset(obj, value)

    def __delete__(self, obj):
        print("in __delete__")
        if self.fdel is None:
            raise AttributeError
        self.fdel(obj)


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

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

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

而後 Student 類,咱們也相應改爲以下class

class Student:
    def __init__(self, name):
        self.name = name

    # 其實只有這裏改變
    @TestProperty
    def math(self):
        return self._math

    @math.setter
    def math(self, value):
        if 0 <= value <= 100:
            self._math = value
        else:
            raise ValueError("Valid value must be in [0, 100]")

爲了儘可能讓你少產生一點疑惑,我這裏作兩點說明:

  1. 使用TestProperty裝飾後,math 再也不是一個函數,而是TestProperty 類的一個實例。因此第二個math函數可使用 math.setter 來裝飾,本質是調用TestProperty.setter 來產生一個新的 TestProperty 實例賦值給第二個math

  2. 第一個 math 和第二個 math 是兩個不一樣 TestProperty 實例。但他們都屬於同一個描述符類(TestProperty),當對 math 對於賦值時,就會進入 TestProperty.__set__,當對math 進行取值裏,就會進入 TestProperty.__get__。仔細一看,其實最終訪問的仍是Student實例的 _math 屬性。

說了這麼多,仍是運行一下,更加直觀一點。

# 運行後,會直接打印這一行,這是在實例化 TestProperty 並賦值給第二個math
in setter
>>>
>>> s1.math = 90
in __set__
>>> s1.math
in __get__
90

對於以上理解 property 的運行原理有困難的同窗,請務必參照我上面寫的兩點說明。若有其餘疑問,能夠加微信與我進行探討。

如何實現staticmethod

說完了 property ,這裏再來說講 @classmethod@staticmethod 的實現原理。

我這裏定義了一個類,用了兩種方式來實現靜態方法。

class Test:
    @staticmethod
    def myfunc():
        print("hello")

# 上下兩種寫法等價

class Test:
    def myfunc():
        print("hello")
    # 重點:這就是描述符的體現
    myfunc = staticmethod(myfunc)

這兩種寫法是等價的,就好像在 property 同樣,其實如下兩種寫法也是等價的。

@TestProperty
def math(self):
    return self._math
  
math = TestProperty(fget=math)

話題仍是轉回到 staticmethod 這邊來吧。

由上面的註釋,能夠看出 staticmethod 其實就至關於一個描述符類,而myfunc 在此刻變成了一個描述符。關於 staticmethod 的實現,你能夠參照下面這段我本身寫的代碼,加以理解。

調用這個方法能夠知道,每調用一次,它都會通過描述符類的 __get__

>>> Test.myfunc()
in staticmethod __get__
hello
>>> Test().myfunc()
in staticmethod __get__
hello

如何實現classmethod

一樣的 classmethod 也是同樣。

class classmethod(object):
    def __init__(self, f):
        self.f = f

    def __get__(self, instance, owner=None):
        print("in classmethod __get__")
        
        def newfunc(*args):
            return self.f(owner, *args)
        return newfunc

class Test:
    def myfunc(cls):
        print("hello")
        
    # 重點:這就是描述符的體現
    myfunc = classmethod(myfunc)

驗證結果以下

>>> Test.myfunc()
in classmethod __get__
hello
>>> Test().myfunc()
in classmethod __get__
hello

講完了 propertystaticmethodclassmethod 與 描述符的關係。我想你應該對描述符在 Python 中的應用有了更深的理解。對於 super 的實現原理,就交由你來本身完成。


關注公衆號,獲取最新干貨!

相關文章
相關標籤/搜索