隨着咱們愈來愈頻繁使用Python, 咱們不免會接觸到類, 接觸到類屬性和方法.可是不少新手包括我, 不知道方法 和 函數 的區別,此次簡單來討論下, 若是有哪裏認識不正確, 但願大神提點指教!
先來看兩個定義吧: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的基礎上, 多了一層類的關係, 正由於這一層類, 因此區分了 function 和 method.而這個過程是經過 PyMethod_New實現的segmentfault
PyObject * PyMethod_New(PyObject *func, PyObject *self, PyObject *klass) { register PyMethodObject *im; // 定義方法結構體 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;
因此本質上, 函數和方法的區別是: 函數是屬於 FunctionObject, 而 方法是屬 PyMethodObject
簡單來看下代碼:python2.7
def aa(d, na=None, *kasd, **kassd): pass class A(object): def f(self): return 1 a = A() print '#### 各自方法描述 ####' print '## 函數 %s' % aa print '## 類方法 %s' % A.f print '## 實例方法 %s' % a.f
輸出結果:編程語言
#### 各自方法描述 #### ## 函數 <function aa at 0x000000000262AB38> ## 類方法 <unbound method A.f> ## 實例方法 <bound method A.f of <__main__.A object at 0x0000000002633198>>
method 還能再分爲 Bound Method 和 Unbound Method, 他們的差異是什麼呢? 差異就是 Bound method 多了一個實例綁定的過程!
A.f 是 unbound method, 而 a.f 是 bound method, 從而驗證了上面的描述是正確的!ide
看到這, 咱們應該會有個問題:函數
方法的綁定, 是何時發生的? 又是怎樣的發生的?
帶着這個問題, 咱們繼續探討.很明顯, 方法的綁定, 確定是伴隨着class的實例化而發生,咱們都知道, 在class裏定義方法, 須要顯示傳入self參數, 由於這個self是表明即將被實例化的對象。
咱們須要dis模塊來協助咱們去觀察這個綁定的過程:code
[root@iZ23pynfq19Z ~]# cat 33.py class A(object): def f(self): return 123 a = A() print A.f() print a.f() ## 命令執行 ## [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輸出說明: 第一列是代碼的行數, 第二列是指令的偏移量, 第三列是可視化指令, 第四列是參數, 第五列是指令根據參數計算或者查找的結果
我們能夠看到 第4列 和第五列, 分別就是對應: print A.f() 和 print a.f()對象
他們都是一樣的字節碼, 都是從所在的codeobject中的co_name取出參數對應的名字, 正由於參數的不一樣, 因此它們分別取到 A 和 a,下面咱們須要來看看 LOAD_ATTR 的做用是什麼:get
//取自: python2.7/objects/ceval.c TARGET(LOAD_ATTR) { w = GETITEM(names, oparg); // 從co_name 取出 f v = TOP(); // 將剛纔壓入棧的 A/a 取出來 x = PyObject_GetAttr(v, w); // 取得真正的執行函數 Py_DECREF(v); SET_TOP(x); if (x != NULL) DISPATCH(); break; }
經過 SET_TOP, 已經將咱們須要真正執行的函數壓入運行時棧, 接下來就是經過 CALL_FUNCTION 來調用這個函數對象, 繼續來看看具體過程:
//取自: 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); // 細節請往下看 #endif stack_pointer = sp; PUSH(x); if (x != NULL) DISPATCH(); break; } static PyObject * call_function(PyObject ***pp_stack, int oparg) { int na = oparg & 0xff; // 位置參數個數 int nk = (oparg>>8) & 0xff; // 關鍵位置參數的個數 int n = na + 2 * nk; // 總的個數和 PyObject **pfunc = (*pp_stack) - n - 1; // 當前棧位置-參數個數,獲得函數對象 PyObject *func = *pfunc; PyObject *x, *w; ... // 省略前面細節, 只看關鍵調用 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); }
我們來捋下調用順序:
CALL_FUNCTION -> call_function -> 根據函數的類型 -> 執行對應的操做
當程序運行到call_function時, 主要有的函數類型判斷有: PyCFunction, PyMethod, PyFunction
在這裏, 虛擬機已經判斷出func是不屬於PyCFunction, 因此將會落入上面源碼的判斷分支中, 而它將要作的,就是分別經過 PyMethod_GET_SELF, PyMethod_GET_FUNCTION 得到self對象和func函數, 而後經過調用 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是用這個self對象替換了pfunc指向的對象了, 而pfunc在上面已經說起到了, 就是當時壓入運行時棧的函數對象. 除了這幾步, 還有更重要的就是, na 和 n 都分別自增1
看回上面的 a.f(), 我們能夠知道, 它是不須要參數的, 因此理論上 na,nk和n都是0, 可是由於f是method(方法), 通過上面一系列操做, 它將會傳入一個self,而na也會變成1, 又由於*pfunc已經被替換成self, 相應代碼:
if (PyFunction_Check(func)) x = fast_function(func, pp_stack, n, na, nk); else x = do_call(func, pp_stack, na, nk);
因此它再也不進入function的尋常路了, 而是走do_call, 而後就開始真正的調用;
其實這個涉及到Python調用函數的整個過程, 由於比較複雜, 後期找個時間專門談談這個
聊到這裏, 咱們已經大體清楚, 一個method(方法) 在調用時所發生的過程.明白了函數和方法的本質區別, 那麼回到主題上 來講下 Unbound 和 Bound, 其實這二者差異也不大. 從上面咱們得知, 一個方法的建立, 是須要self, 而調用時, 也會使用self,而只有實例化對象, 纔有這個self, class是沒有的, 因此像下面的執行, 是失敗的額
class A(object): def f(self): return 1 a = A() print '#### 各自方法等效調用 ####' print '## 類方法 %s' % A.f() print '## 實例方法 %s' % a.f() ## 輸出結果 ## #### 各自方法等效調用 #### 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)
錯誤已經很明顯了: 函數未綁定, 必需要將A的實例做爲第一個參數
既然它要求第一個參數是 A的實例對象, 那咱們就試下修改代碼:
class A(object): def f(self): return 1 a = A() print '#### 各自方法等效調用 ####' print '## 類方法 %s' % A.f(a) #傳入A的實例a print '## 實例方法 %s' % a.f() ## 結果 ## #### 各自方法等效調用 #### ## 類方法 1 ## 實例方法 1
能夠看出來, Bound 和 Unbound判斷的依據就是, 當方法真正執行時, 有沒有傳入實例, A.f(a) 和 a.f() 用法的區別只是在於, 第一種須要人爲傳入實例才能調用, 而第二種, 是虛擬機幫咱們作好了傳入實例的動做, 不用咱們那麼麻煩而已, 兩種方法本質上是等價的
歡迎各位大神指點交流,轉載請註明來源: https://segmentfault.com/a/11...