好吧,我認可我標題黨了。可是這篇文章的知識點,你有極大的可能並不知道。python
前段時間,我寫了一篇描述符的入門級文章,從那些文章裏你知道了如何定義描述符,且明白了描述符是如何工做的。微信
若是你還未學習,能夠點擊這裏進行閱讀:Python爲何要使用描述符函數
正常人所見過的描述符的用法就是上篇文章提到的那些,我想說的是那只是描述符協議最多見的應用之一,或許你還不知道,其實有不少 Python 的特性的底層實現機制都是基於 描述符協議
的,好比咱們熟悉的@property
、@classmethod
、@staticmethod
和 super
等。post
這些裝飾器方法,你絕對熟悉得不得了,可是今天並非要講他們的用法,而是要講是如何本身經過 純Python 實現這些特性。學習
先來講說 property
吧。spa
有了第一篇的基礎,咱們知道了 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
裝飾函數的邏輯代碼塊。cdn
爲何說 property 底層是基於描述符協議的呢?經過 PyCharm 點擊進入 property 的源碼,很惋惜,只是一份相似文檔同樣的僞源碼,並無其具體的實現邏輯。blog
不過,從這份僞源碼的魔法函數結構組成,能夠大致知道其實現邏輯。文檔
這裏我本身經過模仿其函數結構,結合「描述符協議」來本身實現類 property
特性。
代碼以下:
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 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]")
複製代碼
爲了儘可能讓你少產生一點疑惑,我這裏作兩點說明:
使用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
的運行原理有困難的同窗,請務必參照我上面寫的兩點說明。若有其餘疑問,能夠加微信與我進行探討。
說完了 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
也是同樣。
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
複製代碼
講完了 property
、staticmethod
和classmethod
與 描述符的關係。我想你應該對描述符在 Python 中的應用有了更深的理解。對於 super 的實現原理,就交由你來本身完成。