Python: 函式與方法的區別 以及 Bound Method 和 Unbound Method

發表於2017-05-08

函式與方法的區別

隨著我們越來越頻繁使用Python, 我們難免會接觸到類, 接觸到類屬性和方法.但是很多新手包括我, 不知道方法函式 的區別,這次簡單來討論下, 如果有哪裡認識不正確, 希望大神提點指教!
先來看兩個定義吧:

function(函式) —— A series of statements which returns some value toa caller. It can also be passed zero or more arguments which may beused in the execution of the body.
method(方法) —— A function which is defined inside a class body. Ifcalled as an attribute of an instance of that class, the methodwill get the instance object as its first argument (which isusually called self).

從上面可以看出, 別的程式語言一樣, Function也是包含一個函式頭和一個函式體, 也同樣支援0到n個形參,而Method則是在function的基礎上, 多了一層類的關係, 正因為這一層類, 所以區分了 functionmethod.而這個過程是通過 PyMethod_New實現的

所以本質上, 函式和方法的區別是: 函式是屬於 FunctionObject, 而 方法是屬 PyMethodObject
簡單來看下程式碼:

輸出結果:

Bound Method 和 Unbound Method

method 還能再分為 Bound MethodUnbound Method, 他們的差別是什麼呢? 差別就是 Bound method 多了一個例項繫結的過程!
A.funbound method, 而 a.fbound method, 從而驗證了上面的描述是正確的!

看到這, 我們應該會有個問題:

帶著這個問題, 我們繼續探討.很明顯, 方法的繫結, 肯定是伴隨著class的例項化而發生,我們都知道, 在class裡定義方法, 需要顯示傳入self引數, 因為這個self是代表即將被例項化的物件。
我們需要dis模組來協助我們去觀察這個繫結的過程:

dis輸出說明: 第一列是程式碼的函式, 第二列是指令的偏移量, 第三列是視覺化指令, 第四列是引數, 第五列是指令根據引數計算或者查詢的結果
我們們可以看到 第4列 和第五列, 分別就是對應: print A.f() 和 print a.f()

他們都是同樣的位元組碼, 都是從所在的codeobject中的co_name取出引數對應的名字, 正因為引數的不同, 所以它們分別取到 A 和 a,下面我們需要來看看 LOAD_ATTR 的作用是什麼:

通過 SET_TOP, 已經將我們需要真正執行的函式壓入執行時棧, 接下來就是通過 CALL_FUNCTION 來呼叫這個函式物件, 繼續來看看具體過程:

我們們來捋下呼叫順序:

當程式執行到call_function時, 主要有的函式型別判斷有: PyCFunction, PyMethod, PyFunction
在這裡, 虛擬機器已經判斷出func是不屬於PyCFunction, 所以將會落入上面原始碼的判斷分支中, 而它將要做的,就是分別通過 PyMethod_GET_SELF, PyMethod_GET_FUNCTION 獲得self物件和func函式, 然後通過呼叫 Py_SETREF(*pfunc, self):

可以看出, Py_SETREF是用這個self物件替換了pfunc指向的物件了, 而pfunc在上面已經提及到了, 就是當時壓入執行時棧的函式物件. 除了這幾步, 還有更重要的就是, na 和 n 都分別自增1
看回上面的 a.f(), 我們們可以知道, 它是不需要引數的, 所以理論上 na,nk和n都是0, 但是因為f是method(方法), 經過上面一系列操作, 它將會傳入一個self,而na也會變成1, 又因為*pfunc已經被替換成self, 相應程式碼:

所以它不再進入function的尋常路了, 而是走do_call, 然後就開始真正的呼叫;
其實這個涉及到Python呼叫函式的整個過程, 因為比較複雜, 後期找個時間專門談談這個

聊到這裡, 我們已經大致清楚, 一個method(方法) 在呼叫時所發生的過程.明白了函式和方法的本質區別, 那麼回到主題上 來說下 UnboundBound, 其實這兩者差別也不大. 從上面我們得知, 一個方法的建立, 是需要self, 而呼叫時, 也會使用self,而只有例項化物件, 才有這個self, class是沒有的, 所以像下面的執行, 是失敗的額

錯誤已經很明顯了: 函式未繫結, 必須要將A的例項作為第一個引數
既然它要求第一個引數是 A的例項物件, 那我們就試下修改程式碼:

可以看出來, BoundUnbound判斷的依據就是, 當方法真正執行時, 有沒有傳入例項, A.f(a) 和 a.f() 用法的區別只是在於, 第一種需要人為傳入例項才能呼叫, 而第二種, 是虛擬機器幫我們做好了傳入例項的動作, 不用我們那麼麻煩而已, 兩種方法本質上是等價的。

請使用手機”掃一掃”x

相關文章