Python中的函數(shù)與方法 以及Bound Method和Unbound Method
函數(shù)與方法的區(qū)別
隨著我們越來越頻繁使用Python, 我們難免會(huì)接觸到類, 接觸到類屬性和方法.但是很多新手包括我, 不知道方法 和 函數(shù) 的區(qū)別,這次簡單來討論下, 如果有哪里認(rèn)識不正確, 希望大神提點(diǎn)指教!
先來看兩個(gè)定義吧:
function(函數(shù)) —— 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也是包含一個(gè)函數(shù)頭和一個(gè)函數(shù)體, 也同樣支持0到n個(gè)形參,而Method則是在function的基礎(chǔ)上, 多了一層類的關(guān)系, 正因?yàn)檫@一層類, 所以區(qū)分了function 和 method.而這個(gè)過程是通過 PyMethod_New實(shí)現(xiàn)的
- PyObject *
- PyMethod_New(PyObject *func, PyObject *self, PyObject *klass)
- {
- register PyMethodObject *im; // 定義方法結(jié)構(gòu)體
- im = free_list;
- if (im != NULL) {
- free_list = (PyMethodObject *)(im->im_self);
- PyObject_INIT(im, &PyMethod_Type); // 初始化
- numfree--;
- }
- else {
- im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);
- if (im == NULL)
- return NULL;
- }
- im->im_weakreflist = NULL;
- Py_INCREF(func);
- /* 往下開始通過 func 配置 method*/
- im->im_func = func;
- Py_XINCREF(self);
- im->im_self = self;
- Py_XINCREF(klass);
- im->im_class = klass;
- _PyObject_GC_TRACK(im);
- return (PyObject *)im;
所以本質(zhì)上, 函數(shù)和方法的區(qū)別是: 函數(shù)是屬于 FunctionObject, 而 方法是屬 PyMethodObject
簡單來看下代碼:
- def aa(d, na=None, *kasd, **kassd):
- pass
- class A(object):
- def f(self):
- return 1
- a = A()
- print '#### 各自方法描述 ####'
- print '## 函數(shù) %s' % aa
- print '## 類方法 %s' % A.f
- print '## 實(shí)例方法 %s' % a.f
輸出結(jié)果:
- #### 各自方法描述 ####
- ## 函數(shù) <function aa at 0x000000000262AB38>
- ## 類方法 <unbound method A.f>
- ## 實(shí)例方法 <bound method A.f of <__main__.A object at 0x0000000002633198>>
Bound Method 和 Unbound Method
method 還能再分為 Bound Method 和 Unbound Method, 他們的差別是什么呢? 差別就是Bound method 多了一個(gè)實(shí)例綁定的過程!
A.f 是 unbound method, 而 a.f 是 bound method, 從而驗(yàn)證了上面的描述是正確的!
看到這, 我們應(yīng)該會(huì)有個(gè)問題:
方法的綁定, 是什么時(shí)候發(fā)生的? 又是怎樣的發(fā)生的?
帶著這個(gè)問題, 我們繼續(xù)探討.很明顯, 方法的綁定, 肯定是伴隨著class的實(shí)例化而發(fā)生,我們都知道, 在class里定義方法, 需要顯示傳入self參數(shù), 因?yàn)檫@個(gè)self是代表即將被實(shí)例化的對象。
我們需要dis模塊來協(xié)助我們?nèi)ビ^察這個(gè)綁定的過程:
- [root@iZ23pynfq19Z ~]# cat 33.py
- class A(object):
- def f(self):
- return 123
- a = A()
- print A.f()
- print a.f()
- ## 命令執(zhí)行 ##
- [root@iZ23pynfq19Z ~]# python -m dis 33.py
- 1 0 LOAD_CONST 0 ('A')
- 3 LOAD_NAME 0 (object)
- 6 BUILD_TUPLE 1
- 9 LOAD_CONST 1 (<code object A at 0x7fc32f0b5030, file "33.py", line 1>)
- 12 MAKE_FUNCTION 0
- 15 CALL_FUNCTION 0
- 18 BUILD_CLASS
- 19 STORE_NAME 1 (A)
- 4 22 LOAD_NAME 1 (A)
- 25 CALL_FUNCTION 0
- 28 STORE_NAME 2 (a)
- 5 31 LOAD_NAME 1 (A)
- 34 LOAD_ATTR 3 (f)
- 37 CALL_FUNCTION 0
- 40 PRINT_ITEM
- 41 PRINT_NEWLINE
- 6 42 LOAD_NAME 2 (a)
- 45 LOAD_ATTR 3 (f)
- 48 CALL_FUNCTION 0
- 51 PRINT_ITEM
- 52 PRINT_NEWLINE
- 53 LOAD_CONST 2 (None)
- 56 RETURN_VALUE
dis輸出說明: 第一列是代碼的函數(shù), 第二列是指令的偏移量, 第三列是可視化指令, 第四列是參數(shù), 第五列是指令根據(jù)參數(shù)計(jì)算或者查找的結(jié)果
咱們可以看到 第4列 和第五列, 分別就是對應(yīng): print A.f() 和 print a.f()
他們都是同樣的字節(jié)碼, 都是從所在的codeobject中的co_name取出參數(shù)對應(yīng)的名字, 正因?yàn)閰?shù)的不同, 所以它們分別取到 A 和 a,下面我們需要來看看 LOAD_ATTR 的作用是什么:
- //取自: python2.7/objects/ceval.c
- TARGET(LOAD_ATTR)
- {
- w = GETITEM(names, oparg); // 從co_name 取出 f
- v = TOP(); // 將剛才壓入棧的 A/a 取出來
- x = PyObject_GetAttr(v, w); // 取得真正的執(zhí)行函數(shù)
- Py_DECREF(v);
- SET_TOP(x);
- if (x != NULL) DISPATCH();
- break;
- }
通過 SET_TOP, 已經(jīng)將我們需要真正執(zhí)行的函數(shù)壓入運(yùn)行時(shí)棧, 接下來就是通過CALL_FUNCTION 來調(diào)用這個(gè)函數(shù)對象, 繼續(xù)來看看具體過程:
- //取自: python2.7/objects/ceval.c
- TARGET(CALL_FUNCTION)
- {
- PyObject **sp;
- PCALL(PCALL_ALL);
- sp = stack_pointer;
- #ifdef WITH_TSC
- x = call_function(&sp, oparg, &intr0, &intr1);
- #else
- x = call_function(&sp, oparg); // 細(xì)節(jié)請往下看
- #endif
- stack_pointer = sp;
- PUSH(x);
- if (x != NULL) DISPATCH();
- break;
- }
- static PyObject *
- call_function(PyObject ***pp_stack, int oparg)
- {
- int na = oparg & 0xff; // 位置參數(shù)個(gè)數(shù)
- int nk = (oparg>>8) & 0xff; // 關(guān)鍵位置參數(shù)的個(gè)數(shù)
- int n = na + 2 * nk; // 總的個(gè)數(shù)和
- PyObject **pfunc = (*pp_stack) - n - 1; // 當(dāng)前棧位置-參數(shù)個(gè)數(shù),得到函數(shù)對象
- PyObject *func = *pfunc;
- PyObject *x, *w;
- ... // 省略前面細(xì)節(jié), 只看關(guān)鍵調(diào)用
- if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
- /* optimize access to bound methods */
- PyObject *self = PyMethod_GET_SELF(func);
- PCALL(PCALL_METHOD);
- PCALL(PCALL_BOUND_METHOD);
- Py_INCREF(self);
- func = PyMethod_GET_FUNCTION(func);
- Py_INCREF(func);
- Py_SETREF(*pfunc, self);
- na++;
- n++;
- } else
- Py_INCREF(func);
- READ_TIMESTAMP(*pintr0);
- if (PyFunction_Check(func))
- x = fast_function(func, pp_stack, n, na, nk);
- else
- x = do_call(func, pp_stack, na, nk);
- READ_TIMESTAMP(*pintr1);
- Py_DECREF(func);
- }
咱們來捋下調(diào)用順序:
- CALL_FUNCTION -> call_function -> 根據(jù)函數(shù)的類型 -> 執(zhí)行對應(yīng)的操作
當(dāng)程序運(yùn)行到call_function時(shí), 主要有的函數(shù)類型判斷有: PyCFunction, PyMethod, PyFunction
在這里, 虛擬機(jī)已經(jīng)判斷出func是不屬于PyCFunction, 所以將會(huì)落入上面源碼的判斷分支中, 而它將要做的,就是分別通過 PyMethod_GET_SELF, PyMethod_GET_FUNCTION 獲得self對象和func函數(shù), 然后通過調(diào)用 Py_SETREF(*pfunc, self):
- // Py_SETREF 定義如下
- #define Py_SETREF(op, op2)
- do {
- PyObject *_py_tmp = (PyObject *)(op);
- (op) = (op2);
- Py_DECREF(_py_tmp);
- } while (0)
可以看出, Py_SETREF是用這個(gè)self對象替換了pfunc指向的對象了, 而pfunc在上面已經(jīng)提及到了, 就是當(dāng)時(shí)壓入運(yùn)行時(shí)棧的函數(shù)對象. 除了這幾步, 還有更重要的就是, na 和 n 都分別自增1
看回上面的 a.f(), 咱們可以知道, 它是不需要參數(shù)的, 所以理論上 na,nk和n都是0, 但是因?yàn)閒是method(方法), 經(jīng)過上面一系列操作, 它將會(huì)傳入一個(gè)self,而na也會(huì)變成1, 又因?yàn)?pfunc已經(jīng)被替換成self, 相應(yīng)代碼:
- if (PyFunction_Check(func))
- x = fast_function(func, pp_stack, n, na, nk);
- else
- x = do_call(func, pp_stack, na, nk);
所以它不再進(jìn)入function的尋常路了, 而是走do_call, 然后就開始真正的調(diào)用;
其實(shí)這個(gè)涉及到Python調(diào)用函數(shù)的整個(gè)過程, 因?yàn)楸容^復(fù)雜, 后期找個(gè)時(shí)間專門談?wù)勥@個(gè)
聊到這里, 我們已經(jīng)大致清楚, 一個(gè)method(方法) 在調(diào)用時(shí)所發(fā)生的過程.明白了函數(shù)和方法的本質(zhì)區(qū)別, 那么回到主題上 來說下 Unbound 和 Bound, 其實(shí)這兩者差別也不大. 從上面我們得知, 一個(gè)方法的創(chuàng)建, 是需要self, 而調(diào)用時(shí), 也會(huì)使用self,而只有實(shí)例化對象, 才有這個(gè)self, class是沒有的, 所以像下面的執(zhí)行, 是失敗的額
- class A(object):
- def f(self):
- return 1
- a = A()
- print '#### 各自方法等效調(diào)用 ####'
- print '## 類方法 %s' % A.f()
- print '## 實(shí)例方法 %s' % a.f()
- ## 輸出結(jié)果 ##
- #### 各自方法等效調(diào)用 ####
- Traceback (most recent call last):
- File "C:/Users/Administrator/ZGZN_Admin/ZGZN_Admin/1.py", line 20, in <module>
- print '## 類方法 %s' % A.f()
- TypeError: unbound method f() must be called with A instance as first argument (got nothing instead)
錯(cuò)誤已經(jīng)很明顯了: 函數(shù)未綁定, 必須要將A的實(shí)例作為第一個(gè)參數(shù)
既然它要求第一個(gè)參數(shù)是 A的實(shí)例對象, 那我們就試下修改代碼:
- class A(object):
- def f(self):
- return 1
- a = A()
- print '#### 各自方法等效調(diào)用 ####'
- print '## 類方法 %s' % A.f(a) #傳入A的實(shí)例a
- print '## 實(shí)例方法 %s' % a.f()
- ## 結(jié)果 ##
- #### 各自方法等效調(diào)用 ####
- ## 類方法 1
- ## 實(shí)例方法 1
可以看出來, Bound 和 Unbound判斷的依據(jù)就是, 當(dāng)方法真正執(zhí)行時(shí), 有沒有傳入實(shí)例, A.f(a) 和 a.f() 用法的區(qū)別只是在于, 第一種需要人為傳入實(shí)例才能調(diào)用, 而第二種, 是虛擬機(jī)幫我們做好了傳入實(shí)例的動(dòng)作, 不用我們那么麻煩而已, 兩種方法本質(zhì)上是等價(jià)的。