摘要:由上面的注釋,可以看出其實(shí)就相當(dāng)于一個(gè)描述符類,而在此刻變成了一個(gè)描述符。調(diào)用這個(gè)方法可以知道,每調(diào)用一次,它都會(huì)經(jīng)過描述符類的。基于描述符如何實(shí)現(xiàn)同樣的也是一樣。我想你應(yīng)該對描述符在中的應(yīng)用有了更深的理解。
好吧,我承認(rèn)我標(biāo)題黨了。但是這篇文章的知識(shí)點(diǎn),你有極大的可能并不知道。
前段時(shí)間,我寫了一篇描述符的入門級(jí)文章,從那些文章里你知道了如何定義描述符,且明白了描述符是如何工作的。
如果你還未學(xué)習(xí),可以點(diǎn)擊這里進(jìn)行閱讀:Python為什么要使用描述符
正常人所見過的描述符的用法就是上篇文章提到的那些,我想說的是那只是描述符協(xié)議最常見的應(yīng)用之一,或許你還不知道,其實(shí)有很多 Python 的特性的底層實(shí)現(xiàn)機(jī)制都是基于 描述符協(xié)議 的,比如我們熟悉的@property 、@classmethod 、@staticmethod 和 super 等。
這些裝飾器方法,你絕對熟悉得不得了,但是今天并不是要講他們的用法,而是要講是如何自己通過 純Python 實(shí)現(xiàn)這些特性。
先來說說 property 吧。
有了第一篇的基礎(chǔ),我們知道了 property 的基本用法。這里我直接切入主題,從第一篇的例子里精簡了一下。
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裝飾的函數(shù),如例子中的 math 會(huì)變成 Student 實(shí)例的屬性。而對 math 屬性賦值會(huì)進(jìn)入 使用 math.setter 裝飾函數(shù)的邏輯代碼塊。
為什么說 property 底層是基于描述符協(xié)議的呢?通過 PyCharm 點(diǎn)擊進(jìn)入 property 的源碼,很可惜,只是一份類似文檔一樣的偽源碼,并沒有其具體的實(shí)現(xiàn)邏輯。
不過,從這份偽源碼的魔法函數(shù)結(jié)構(gòu)組成,可以大體知道其實(shí)現(xiàn)邏輯。
這里我自己通過模仿其函數(shù)結(jié)構(gòu),結(jié)合「描述符協(xié)議」來自己實(shí)現(xiàn)類 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 類,我們也相應(yīng)改成如下
class Student:
def __init__(self, name):
self.name = name
# 其實(shí)只有這里改變
@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]")
為了盡量讓你少產(chǎn)生一點(diǎn)疑惑,我這里做兩點(diǎn)說明:
使用TestProperty裝飾后,math 不再是一個(gè)函數(shù),而是TestProperty 類的一個(gè)實(shí)例。所以第二個(gè)math函數(shù)可以使用 math.setter 來裝飾,本質(zhì)是調(diào)用TestProperty.setter 來產(chǎn)生一個(gè)新的 TestProperty 實(shí)例賦值給第二個(gè)math。
第一個(gè) math 和第二個(gè) math 是兩個(gè)不同 TestProperty 實(shí)例。但他們都屬于同一個(gè)描述符類(TestProperty),當(dāng)對 math 對于賦值時(shí),就會(huì)進(jìn)入 TestProperty.__set__,當(dāng)對math 進(jìn)行取值里,就會(huì)進(jìn)入 TestProperty.__get__。仔細(xì)一看,其實(shí)最終訪問的還是Student實(shí)例的 _math 屬性。
說了這么多,還是運(yùn)行一下,更加直觀一點(diǎn)。
# 運(yùn)行后,會(huì)直接打印這一行,這是在實(shí)例化 TestProperty 并賦值給第二個(gè)math
in setter
>>>
>>> s1.math = 90
in __set__
>>> s1.math
in __get__
90
對于以上理解 property 的運(yùn)行原理有困難的同學(xué),請務(wù)必參照我上面寫的兩點(diǎn)說明。如有其他疑問,可以加微信與我進(jìn)行探討。
1.17.4 基于描述符如何實(shí)現(xiàn)staticmethod說完了 property ,這里再來講講 @classmethod 和 @staticmethod 的實(shí)現(xiàn)原理。
我這里定義了一個(gè)類,用了兩種方式來實(shí)現(xiàn)靜態(tài)方法。
class Test:
@staticmethod
def myfunc():
print("hello")
# 上下兩種寫法等價(jià)
class Test:
def myfunc():
print("hello")
# 重點(diǎn):這就是描述符的體現(xiàn)
myfunc = staticmethod(myfunc)
這兩種寫法是等價(jià)的,就好像在 property 一樣,其實(shí)以下兩種寫法也是等價(jià)的。
@TestProperty
def math(self):
return self._math
math = TestProperty(fget=math)
話題還是轉(zhuǎn)回到 staticmethod 這邊來吧。
由上面的注釋,可以看出 staticmethod 其實(shí)就相當(dāng)于一個(gè)描述符類,而myfunc 在此刻變成了一個(gè)描述符。關(guān)于 staticmethod 的實(shí)現(xiàn),你可以參照下面這段我自己寫的代碼,加以理解。
調(diào)用這個(gè)方法可以知道,每調(diào)用一次,它都會(huì)經(jīng)過描述符類的 __get__ 。
>>> Test.myfunc()
in staticmethod __get__
hello
>>> Test().myfunc()
in staticmethod __get__
hello
1.17.4 基于描述符如何實(shí)現(xiàn)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")
# 重點(diǎn):這就是描述符的體現(xiàn)
myfunc = classmethod(myfunc)
驗(yàn)證結(jié)果如下
>>> Test.myfunc()
in classmethod __get__
hello
>>> Test().myfunc()
in classmethod __get__
hello
講完了 property、staticmethod和classmethod 與 描述符的關(guān)系。我想你應(yīng)該對描述符在 Python 中的應(yīng)用有了更深的理解。對于 super 的實(shí)現(xiàn)原理,就交由你來自己完成。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/6982.html
摘要:由上面的注釋,可以看出其實(shí)就相當(dāng)于一個(gè)描述符類,而在此刻變成了一個(gè)描述符。調(diào)用這個(gè)方法可以知道,每調(diào)用一次,它都會(huì)經(jīng)過描述符類的。基于描述符如何實(shí)現(xiàn)同樣的也是一樣。我想你應(yīng)該對描述符在中的應(yīng)用有了更深的理解。好吧,我承認(rèn)我標(biāo)題黨了。但是這篇文章的知識(shí)點(diǎn),你有極大的可能并不知道。 前段時(shí)間,我寫了一篇描述符的入門級(jí)文章,從那些文章里你知道了如何定義描述符,且明白了描述符是如何工作的。 如果你還...
摘要:事實(shí)上實(shí)例的實(shí)現(xiàn)方式與上面的實(shí)例類似。其次,為了實(shí)現(xiàn)確實(shí)對屬性的調(diào)用順序做出了相應(yīng)的調(diào)整,這些將會(huì)的下中介紹。參考資料如何理解的中基于的一些概念上中基于的一些概念下的官方文檔描述符解密的官方文檔 Python 在 2.2 版本中引入了descriptor(描述符)功能,也正是基于這個(gè)功能實(shí)現(xiàn)了新式類(new-styel class)的對象模型,同時(shí)解決了之前版本中經(jīng)典類 (classi...
摘要:最近在閱讀微型框架的源碼,發(fā)現(xiàn)了中有一個(gè)既是裝飾器類又是描述符的有趣實(shí)現(xiàn)。所以第三版的代碼可以這樣寫第三版的代碼沒有使用裝飾器,而是使用了描述符這個(gè)技巧。更大的問題來自如何將描述符與裝飾器結(jié)合起來,因?yàn)槭且粋€(gè)類而不是方法。 最近在閱讀Python微型Web框架Bottle的源碼,發(fā)現(xiàn)了Bottle中有一個(gè)既是裝飾器類又是描述符的有趣實(shí)現(xiàn)。剛好這兩個(gè)點(diǎn)是Python比較的難理解,又混合在...
摘要:之所以是這樣是因?yàn)楫?dāng)訪問一個(gè)實(shí)例描述符對象時(shí),會(huì)將轉(zhuǎn)換為。而類的字典中則有描述符對象。這主要就是因?yàn)槊枋龇麅?yōu)先。此外,非數(shù)據(jù)描述符的優(yōu)先級(jí)低于實(shí)例屬性。參考以上就是本人對描述符的一些理解,有什么不正確的地方還請不吝指出,謝謝 什么是描述符 python描述符是一個(gè)綁定行為的對象屬性,在描述符協(xié)議中,它可以通過方法重寫屬性的訪問。這些方法有 __get__(), __set__(), 和...
閱讀 1225·2021-11-11 16:54
閱讀 1738·2021-10-13 09:40
閱讀 933·2021-10-08 10:05
閱讀 3498·2021-09-22 15:50
閱讀 3701·2021-09-22 15:41
閱讀 1782·2021-09-22 15:08
閱讀 2338·2021-09-07 10:24
閱讀 3571·2019-08-30 12:52