函數(shù)與方法的區(qū)別
隨著我們?cè)絹?lái)越頻繁使用Python, 我們難免會(huì)接觸到類, 接觸到類屬性和方法.但是很多新手包括我, 不知道方法 和 函數(shù) 的區(qū)別,這次簡(jiǎn)單來(lái)討論下, 如果有哪里認(rèn)識(shí)不正確, 希望大神提點(diǎn)指教!
先來(lái)看兩個(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).
從上面可以看出, 別的編程語(yǔ)言一樣, Function也是包含一個(gè)函數(shù)頭和一個(gè)函數(shù)體, 也同樣支持0到n個(gè)形參,而Method則是在function的基礎(chǔ)上, 多了一層類的關(guān)系, 正因?yàn)檫@一層類, 所以區(qū)分了function 和 method.而這個(gè)過(guò)程是通過(guò) 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);
/* 往下開(kāi)始通過(guò) 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
簡(jiǎn)單來(lái)看下代碼:
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ù)?
## 類方法?
## 實(shí)例方法 >
Bound Method 和 Unbound Method
method 還能再分為 Bound Method 和 Unbound Method, 他們的差別是什么呢? 差別就是Bound method 多了一個(gè)實(shí)例綁定的過(guò)程!
A.f 是 unbound method, 而 a.f 是 bound method, 從而驗(yàn)證了上面的描述是正確的!
看到這, 我們應(yīng)該會(huì)有個(gè)問(wèn)題:
方法的綁定, 是什么時(shí)候發(fā)生的? 又是怎樣的發(fā)生的?
帶著這個(gè)問(wèn)題, 我們繼續(xù)探討.很明顯, 方法的綁定, 肯定是伴隨著class的實(shí)例化而發(fā)生,我們都知道, 在class里定義方法, 需要顯示傳入self參數(shù), 因?yàn)檫@個(gè)self是代表即將被實(shí)例化的對(duì)象。
我們需要dis模塊來(lái)協(xié)助我們?nèi)ビ^察這個(gè)綁定的過(guò)程:
[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 ()
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輸出說(shuō)明: 第一列是代碼的函數(shù), 第二列是指令的偏移量, 第三列是可視化指令, 第四列是參數(shù), 第五列是指令根據(jù)參數(shù)計(jì)算或者查找的結(jié)果
咱們可以看到 第4列 和第五列, 分別就是對(duì)應(yīng): print A.f() 和 print a.f()
他們都是同樣的字節(jié)碼, 都是從所在的codeobject中的co_name取出參數(shù)對(duì)應(yīng)的名字, 正因?yàn)閰?shù)的不同, 所以它們分別取到 A 和 a,下面我們需要來(lái)看看 LOAD_ATTR 的作用是什么:
//取自: python2.7/objects/ceval.c
TARGET(LOAD_ATTR)
{
w = GETITEM(names, oparg);? // 從co_name 取出 f
v = TOP();? ? ? ? ? ? ? ? ? // 將剛才壓入棧的 A/a 取出來(lái)
x = PyObject_GetAttr(v, w); // 取得真正的執(zhí)行函數(shù)
Py_DECREF(v);
SET_TOP(x);
if (x != NULL) DISPATCH();
break;
}
通過(guò) SET_TOP, 已經(jīng)將我們需要真正執(zhí)行的函數(shù)壓入運(yùn)行時(shí)棧, 接下來(lái)就是通過(guò)CALL_FUNCTION 來(lái)調(diào)用這個(gè)函數(shù)對(duì)象, 繼續(xù)來(lái)看看具體過(guò)程:
//取自: 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é)請(qǐng)往下看
#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ù)對(duì)象
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);
}
咱們來(lái)捋下調(diào)用順序:
CALL_FUNCTION -> call_function -> 根據(jù)函數(shù)的類型 -> 執(zhí)行對(duì)應(yīng)的操作
當(dāng)程序運(yùn)行到call_function時(shí), 主要有的函數(shù)類型判斷有: PyCFunction, PyMethod, PyFunction
在這里, 虛擬機(jī)已經(jīng)判斷出func是不屬于PyCFunction, 所以將會(huì)落入上面源碼的判斷分支中, 而它將要做的,就是分別通過(guò) PyMethod_GET_SELF, PyMethod_GET_FUNCTION 獲得self對(duì)象和func函數(shù), 然后通過(guò)調(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對(duì)象替換了pfunc指向的對(duì)象了, 而pfunc在上面已經(jīng)提及到了, 就是當(dāng)時(shí)壓入運(yùn)行時(shí)棧的函數(shù)對(duì)象. 除了這幾步, 還有更重要的就是, na 和 n 都分別自增1
看回上面的 a.f(), 咱們可以知道, 它是不需要參數(shù)的, 所以理論上 na,nk和n都是0, 但是因?yàn)閒是method(方法), 經(jīng)過(guò)上面一系列操作, 它將會(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, 然后就開(kāi)始真正的調(diào)用;
其實(shí)這個(gè)涉及到Python調(diào)用函數(shù)的整個(gè)過(guò)程, 因?yàn)楸容^復(fù)雜, 后期找個(gè)時(shí)間專門(mén)談?wù)勥@個(gè)
聊到這里, 我們已經(jīng)大致清楚, 一個(gè)method(方法) 在調(diào)用時(shí)所發(fā)生的過(guò)程.明白了函數(shù)和方法的本質(zhì)區(qū)別, 那么回到主題上 來(lái)說(shuō)下 Unbound 和 Bound, 其實(shí)這兩者差別也不大. 從上面我們得知, 一個(gè)方法的創(chuàng)建, 是需要self, 而調(diào)用時(shí), 也會(huì)使用self,而只有實(shí)例化對(duì)象, 才有這個(gè)self, class是沒(méi)有的, 所以像下面的執(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
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í)例對(duì)象, 那我們就試下修改代碼:
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
可以看出來(lái), Bound 和 Unbound判斷的依據(jù)就是, 當(dāng)方法真正執(zhí)行時(shí), 有沒(méi)有傳入實(shí)例, A.f(a) 和 a.f() 用法的區(qū)別只是在于, 第一種需要人為傳入實(shí)例才能調(diào)用, 而第二種, 是虛擬機(jī)幫我們做好了傳入實(shí)例的動(dòng)作, 不用我們那么麻煩而已, 兩種方法本質(zhì)上是等價(jià)的。我有建立一個(gè)python學(xué)習(xí)交流群,在群里我們相互幫助,相互關(guān)心,相互分享內(nèi)容,這樣出問(wèn)題幫助你的人就比較多,群號(hào)是301,還有056,最后是051,這樣就可以找到大神聚合的群,如果你只愿意別人幫助你,不愿意分享或者幫助別人,那就請(qǐng)不要加了,你把你會(huì)的告訴別人這是一種分享。如果你看了覺(jué)得還可以的麻煩給我點(diǎn)個(gè)贊謝謝