Python: 函数与方法的区别 以及 Bound Method 和 Unbound Method

函数与方法的区别

随着咱们愈来愈频繁使用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的基础上, 多了一层类的关系, 正由于这一层类, 因此区分了 functionmethod.而这个过程是经过 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>>

Bound Method 和 Unbound Method

method 还能再分为 Bound MethodUnbound Method, 他们的差异是什么呢? 差异就是 Bound method 多了一个实例绑定的过程!
A.funbound method, 而 a.fbound 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(方法) 在调用时所发生的过程.明白了函数和方法的本质区别, 那么回到主题上 来讲下 UnboundBound, 其实这二者差异也不大. 从上面咱们得知, 一个方法的建立, 是须要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

能够看出来, BoundUnbound判断的依据就是, 当方法真正执行时, 有没有传入实例, A.f(a) 和 a.f() 用法的区别只是在于, 第一种须要人为传入实例才能调用, 而第二种, 是虚拟机帮咱们作好了传入实例的动做, 不用咱们那么麻烦而已, 两种方法本质上是等价的
欢迎各位大神指点交流,转载请注明来源: https://segmentfault.com/a/11...

相关文章
相关标签/搜索