Python中的函數(shù)與方法 以及python類機(jī)制

函數(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è)贊謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評(píng)論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,034評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,413評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,449評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,165評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,559評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,781評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,327評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,084評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,278評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,495評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,927評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,172評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,010評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,241評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容