天天看點

Python中的函數與方法 以及Bound Method和Unbound Method

Python中的函數與方法 以及Bound Method和Unbound Method

函數與方法的差別

随着我們越來越頻繁使用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實作的

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

簡單來看下代碼:

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>>  

bound method 和 unbound method

method 還能再分為 bound method 和 unbound method, 他們的差别是什麼呢? 差别就是bound method 多了一個執行個體綁定的過程!

a.f 是 unbound method, 而 a.f 是 bound method, 進而驗證了上面的描述是正确的!

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

方法的綁定, 是什麼時候發生的? 又是怎樣的發生的?

帶着這個問題, 我們繼續探讨.很明顯, 方法的綁定, 肯定是伴随着class的執行個體化而發生,我們都知道, 在class裡定義方法, 需要顯示傳入self參數, 因為這個self是代表即将被執行個體化的對象。

我們需要dis子產品來協助我們去觀察這個綁定的過程:

[root@iz23pynfq19z ~]# cat 33.py 

        return 123 

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 的作用是什麼:

//取自: 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 來調用這個函數對象, 繼續來看看具體過程:

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); 

        } 

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 

        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 = do_call(func, pp_stack, na, nk);  

是以它不再進入function的尋常路了, 而是走do_call, 然後就開始真正的調用;

其實這個涉及到python調用函數的整個過程, 因為比較複雜, 後期找個時間專門談談這個

聊到這裡, 我們已經大緻清楚, 一個method(方法) 在調用時所發生的過程.明白了函數和方法的本質差別, 那麼回到主題上 來說下

unbound 和 bound, 其實這兩者差别也不大. 從上面我們得知, 一個方法的建立, 是需要self, 而調用時,

也會使用self,而隻有執行個體化對象, 才有這個self, class是沒有的, 是以像下面的執行, 是失敗的額

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的執行個體對象, 那我們就試下修改代碼:

print '## 類方法 %s' % a.f(a)   #傳入a的執行個體a 

## 結果 ## 

## 類方法 1 

## 執行個體方法 1  

可以看出來, bound 和 unbound判斷的依據就是, 當方法真正執行時, 有沒有傳入執行個體, a.f(a) 和 a.f()

用法的差別隻是在于, 第一種需要人為傳入執行個體才能調用, 而第二種, 是虛拟機幫我們做好了傳入執行個體的動作, 不用我們那麼麻煩而已,

兩種方法本質上是等價的。

作者:佚名

來源:51cto