90 % 的 Python 开发者不知道的描述符应用
好吧,我承認我標題黨了。但是這篇文章的知識點,你有極大的可能并不知道。
前段時間,我寫了一篇描述符的入門級文章,從那些文章里你知道了如何定義描述符,且明白了描述符是如何工作的。
如果你還未學習,可以點擊這里進行閱讀:Python為什么要使用描述符
正常人所見過的描述符的用法就是上篇文章提到的那些,我想說的是那只是描述符協議最常見的應用之一,或許你還不知道,其實有很多 Python 的特性的底層實現機制都是基于 描述符協議 的,比如我們熟悉的@property 、@classmethod 、@staticmethod 和 super 等。
這些裝飾器方法,你絕對熟悉得不得了,但是今天并不是要講他們的用法,而是要講是如何自己通過 純Python 實現這些特性。
先來說說 property 吧。
有了第一篇的基礎,我們知道了 property 的基本用法。這里我直接切入主題,從第一篇的例子里精簡了一下。
class Student:def __init__(self, name):self.name = namedef math(self):return self._mathdef math(self, value):if 0 <= value <= 100:self._math = valueelse:raise ValueError("Valid value must be in [0, 100]") 復制代碼不防再簡單回顧一下它的用法,通過property裝飾的函數,如例子中的 math 會變成 Student 實例的屬性。而對 math 屬性賦值會進入 使用 math.setter 裝飾函數的邏輯代碼塊。
為什么說 property 底層是基于描述符協議的呢?通過 PyCharm 點擊進入 property 的源碼,很可惜,只是一份類似文檔一樣的偽源碼,并沒有其具體的實現邏輯。
不過,從這份偽源碼的魔法函數結構組成,可以大體知道其實現邏輯。
這里我自己通過模仿其函數結構,結合「描述符協議」來自己實現類 property 特性。
代碼如下:
class TestProperty(object):def __init__(self, fget=None, fset=None, fdel=None, doc=None):self.fget = fgetself.fset = fsetself.fdel = fdelself.__doc__ = docdef __get__(self, obj, objtype=None):print("in __get__")if obj is None:return selfif self.fget is None:raise AttributeErrorreturn self.fget(obj)def __set__(self, obj, value):print("in __set__")if self.fset is None:raise AttributeErrorself.fset(obj, value)def __delete__(self, obj):print("in __delete__")if self.fdel is None:raise AttributeErrorself.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 Student:def __init__(self, name):self.name = name# 其實只有這里改變 def math(self):return self._mathdef math(self, value):if 0 <= value <= 100:self._math = valueelse:raise ValueError("Valid value must be in [0, 100]") 復制代碼為了盡量讓你少產生一點疑惑,我這里做兩點說明:
使用TestProperty裝飾后,math 不再是一個函數,而是TestProperty 類的一個實例。所以第二個math函數可以使用 math.setter 來裝飾,本質是調用TestProperty.setter 來產生一個新的 TestProperty 實例賦值給第二個math。
第一個 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 的運行原理有困難的同學,請務必參照我上面寫的兩點說明。如有其他疑問,可以加微信與我進行探討。
1.17.4 基于描述符如何實現staticmethod
說完了 property ,這里再來講講 @classmethod 和 @staticmethod 的實現原理。
我這里定義了一個類,用了兩種方式來實現靜態方法。
class Test: def myfunc():print("hello")# 上下兩種寫法等價class Test:def myfunc():print("hello")# 重點:這就是描述符的體現myfunc = staticmethod(myfunc) 復制代碼這兩種寫法是等價的,就好像在 property 一樣,其實以下兩種寫法也是等價的。
def math(self):return self._mathmath = TestProperty(fget=math) 復制代碼話題還是轉回到 staticmethod 這邊來吧。
由上面的注釋,可以看出 staticmethod 其實就相當于一個描述符類,而myfunc 在此刻變成了一個描述符。關于 staticmethod 的實現,你可以參照下面這段我自己寫的代碼,加以理解。
調用這個方法可以知道,每調用一次,它都會經過描述符類的 __get__ 。
Test.myfunc() in staticmethod __get__ hello Test().myfunc() in staticmethod __get__ hello 復制代碼1.17.4 基于描述符如何實現classmethod
同樣的 classmethod 也是一樣。
class classmethod(object):def __init__(self, f):self.f = fdef __get__(self, instance, owner=None):print("in classmethod __get__")def newfunc(*args):return self.f(owner, *args)return newfuncclass Test:def myfunc(cls):print("hello")# 重點:這就是描述符的體現myfunc = classmethod(myfunc) 復制代碼驗證結果如下
Test.myfunc() in classmethod __get__ hello Test().myfunc() in classmethod __get__ hello 復制代碼講完了 property、staticmethod和classmethod 與 描述符的關系。我想你應該對描述符在 Python 中的應用有了更深的理解。對于 super 的實現原理,就交由你來自己完成。
總結
以上是生活随笔為你收集整理的90 % 的 Python 开发者不知道的描述符应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++中void和void*指针的含义
- 下一篇: 48-如何实现unix2dos功能