標籤:

python的descriptor的意圖是什麼,想知道python當初弄出功能的意圖?困擾我好久了。


你想要了解語言添加某個功能的意圖,就需要去了解這門語言的歷史。而了解某門語言的春秋筆法,最好的途徑是去看這門語言設計者的文章。

學C++最值得看的書是Bjarne Stroustrup的《The Design and Evolution of C++》,而了解Python也不應該錯過Guido van Rossum的《The History of Python》系列blog。

Descriptors 是bound methods這一概念的泛化,而bound methods是舊式類實現的核心。舊式類情況下在實例的屬性查找過程中,如果在實例字典(instance dictionary)中找不到實例屬性時,就會繼續在類字典中(class dictionary)查找,然後是遞歸遍歷基類的類字典。如果屬性在類字典中被找到(而非在實例字典中找到),解釋器會檢查所找到的對象是否為函數對象。如果是函數對象,那麼解釋器會返回該函數對象的一個wrapper,而非返回該函數對象本身。當這個wrapper被調用時,它會將實例作為第1個參數插入到原先函數調用時的參數列表中,然後調用原先的函數對象。

舉個例子,假設類C有一個實例x,如果調用x.f(0),那麼解釋器會去查找實例x中名為「f」的屬性,然後用參數0進行調用。如果,「f」是類中定義的方法,那麼這個屬性請求(attribute request )將會返回一個包裝函數(wrapper function),這個
wrapper約等於如下python偽代碼:

def bound_f(arg):
return f(x, arg)

當傳遞參數0來調用這個wrapper時,它會傳遞2個參數,x和0,來調用「f」。通過這個機制,類中的方法可以拿到它們的「self」參數。

另一方面,如果查找到的屬性非函數類型,則不創建wrapper,解釋器不做變化直接返回此類屬性。

在Python中,不光類可以定義方法,實例自身也可以定義方法,區別在於類中定義的方法多了1個self參數,但是這兩者,都能夠以instance.bar()的形式調用,核心就在於上文所述的bound methods。同樣是想拿到instance.bar的值,對於類中的方法解釋器返回的是一個綁定了instance參數的wrapper,而對於實例自身的方法,解釋器直接返回函數對象本身。

但是,這種機制是存在局限的。局限之一是這種機制下無法創建混合類,也就是部分方法用Python實現,部分方法用C實現的類,因為這種機制下只有Python方法函數會才會被包裝,使得調用時能訪問實例,而且這一行為是硬編碼在語言中的(事實上,也不符合Python duck typing的設計理念,每次屬性搜索拿到對象都要判斷它的類型是什麼,而不是它能做什麼)。同時,也沒有明確的方式來定義不同類型的方法,例如C++和Java程序員所熟悉的靜態成員函數。

為了解決以上問題,Python 2.2對上述包裝行為做了簡單的泛化。不再硬編碼對Python函數對象進行包裝而其他類型不做包裝的行為,轉而將包裝行為留給通過屬性搜索找到的對象(上述例子中是函數f)來處理。如果屬性搜索找到的對象有__get__方法,那麼這個對象被認為是一個descriptor object。此時__get__方法會被調用,其返回值作為屬性搜索的最終結果。如果這個對象沒有__get__方法,它將不做變化直接返回。這樣,之前需要在實例屬性搜索代碼中做特殊處理來獲得wrapper,而現在函數對象僅需通過__get__方法來返回wrapper。

如此一來,將函數對象本身就做成一個descriptor,根據descriptor協議,實例調用類中的方法時,才會觸發__get__請求,返回wrapper。顯然,descriptor的提出才是符合Python設計哲學的。


給你個hook定製訪問行為唄。


手機答題。

主要是便於抽象getter 和 setter的行為,比如Django的orm里的field。

你會發現很多語言如果getter或者setter之間有類似的行為,你是無法方便地DRY的。

當然,整個設計模式換掉就另當別論了。


這個東西可以拿來做 Data Accessing 的 AOP。

比如加密,訪問控制,等等。

Aspect-oriented programming


主要作用是控制屬性的訪問規則。

描述符是一個具有``綁定行為``的對象屬性。這句話的意思是它一般是作為對象的屬性,只是它的訪問規則和普通的屬性不完全相同。

一個對象中只要包含了

__get__(), __set__()和__delete__()

這三個方法(包含至少一個),就稱它為描述符。

屬性訪問的默認行為是從一個對象的字典(``__dict__``)中獲取 (get)、設置 (set)、刪除 (delete) 屬性。

例如:``a.x`` 的查找鏈始於 ``a.__dict__[x]``,然後是 ``type(a).__dict__[x]``,然後是 ``type(a)`` 除元類之外的基類(如果繼承樹很深,可能會訪問多個基類)。如果查找到的值是一個描述符,那麼Python可能不會按照默認的屬性訪問規則而依據描述符規則訪問該屬性了。

注意:只有在新式對象或者新式類(繼承自``object``或者``type``)中描述符才會被調用。

描述符是屬性、方法、靜態方法、類方法、``super()``背後的實現機制。它被廣泛應用於Python 2.2中用來實現新式類(當然之後的Python版本中基本用C實現這些機制)。描述符簡化了底層的C代碼並為Python編程提供了一套靈活的新工具。

想要更深入的理解它,可以看這篇指南:Descriptor HowTo Guide,很詳細了。

看完理解之後應該就能解釋你``descriptor意圖``的困惑了,再有不懂歡迎交流。


說一個descriptor的應用,python的實例方法其實就是個實現了descriptor協議的函數對象,你每次調用類的實例方法,python會自動把該實例跟這個函數的第一個參數(通常是self)綁定,綁定後的函數對象就叫bound method。這也是為啥不管你給實例方法第一個參數起啥名,它總是跟當前實例綁在一起。


推薦閱讀:

Python 的 Metaclass 有沒有什麼好的 Best Practice 可以學習?
拆代碼學演算法之用python實現KNN過程詳解
Python黑帽編程 3.3 MAC洪水攻擊

TAG:Python |