Python: 函数与方法的区别 以及 Bound Method 和 Unbound Method
LucieSettle
7年前
<h2>函数与方法的区别</h2> <p>随着我们越来越频繁使用Python, 我们难免会接触到类, 接触到类属性和方法.但是很多新手包括我, 不知道 <strong>方法</strong> 和 <strong>函数</strong> 的区别,这次简单来讨论下, 如果有哪里认识不正确, 希望大神提点指教!</p> <p>先来看两个定义吧:</p> <p>function( <strong> <em>函数</em> </strong> ) —— 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.</p> <p>method( <strong> <em>方法</em> </strong> ) —— 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).</p> <p>从上面可以看出, 别的编程语言一样, Function也是包含一个函数头和一个函数体, 也同样支持0到n个形参,而Method则是在function的基础上, 多了一层类的关系, 正因为这一层类, 所以区分了 <strong>function</strong> 和 <strong>method</strong> .而这个过程是通过 <strong>PyMethod_New</strong> 实现的</p> <pre> <code class="language-python">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; </code></pre> <p>所以本质上, 函数和方法的区别是: 函数是属于 <strong>FunctionObject</strong> , 而 方法是属 <strong>PyMethodObject</strong></p> <p>简单来看下代码:</p> <pre> <code class="language-python">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 </code></pre> <p>输出结果:</p> <pre> <code class="language-python">#### 各自方法描述 #### ## 函数 <function aa at 0x000000000262AB38> ## 类方法 <unbound method A.f> ## 实例方法 <bound method A.f of <__main__.A object at 0x0000000002633198>> </code></pre> <h2>Bound Method 和 Unbound Method</h2> <p>method还能再分为 <strong>Bound Method</strong> 和 <strong>Unbound Method</strong> , 他们的差别是什么呢? 差别就是 <strong>Bound method</strong> 多了一个 <strong>实例绑定</strong> 的过程!</p> <p>A.f是 <strong>unbound method</strong> , 而 <strong>a.f</strong> 是 <strong>bound method</strong> , 从而验证了上面的描述是正确的!</p> <p>看到这, 我们应该会有个问题:</p> <pre> <code class="language-python"> 方法的绑定, 是什么时候发生的? 又是怎样的发生的? </code></pre> <p>带着这个问题, 我们继续探讨.很明显, 方法的绑定, 肯定是伴随着 <strong>class</strong> 的实例化而发生,我们都知道, 在 <strong>class</strong> 里定义方法, 需要显示传入 <strong>self</strong> 参数, 因为这个 <strong>self</strong> 是代表即将被实例化的对象。</p> <p>我们需要 <strong>dis</strong> 模块来协助我们去观察这个绑定的过程:</p> <pre> <code class="language-python">[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 </code></pre> <p>dis输出说明: 第一列是代码的函数, 第二列是指令的偏移量, 第三列是可视化指令, 第四列是参数, 第五列是指令根据参数计算或者查找的结果</p> <p>咱们可以看到 第4列 和第五列, 分别就是对应: print A.f() 和 print a.f()</p> <p>他们都是同样的字节码, 都是从所在的codeobject中的 <strong>co_name</strong> 取出参数对应的名字, 正因为参数的不同, 所以它们分别取到 A 和 a,下面我们需要来看看 <strong>LOAD_ATTR</strong> 的作用是什么:</p> <pre> <code class="language-python">//取自: 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; } </code></pre> <p>通过 <strong>SET_TOP</strong> , 已经将我们需要真正执行的函数压入运行时栈, 接下来就是通过 <strong>CALL_FUNCTION</strong> 来调用这个函数对象, 继续来看看具体过程:</p> <pre> <code class="language-python">//取自: 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); } </code></pre> <p>咱们来捋下调用顺序:</p> <pre> <code class="language-python">CALL_FUNCTION -> call_function -> 根据函数的类型 -> 执行对应的操作 </code></pre> <p>当程序运行到 <strong>call_function</strong> 时, 主要有的函数类型判断有: <strong>PyCFunction, PyMethod, PyFunction</strong></p> <p>在这里, 虚拟机已经判断出func是不属于 <strong>PyCFunction</strong> , 所以将会落入上面源码的判断分支中, 而它将要做的,就是分别通过 <strong>PyMethod_GET_SELF, PyMethod_GET_FUNCTION</strong> 获得self对象和func函数, 然后通过调用 <strong>Py_SETREF</strong> (*pfunc, self):</p> <pre> <code class="language-python">// Py_SETREF 定义如下 #define Py_SETREF(op, op2) do { PyObject *_py_tmp = (PyObject *)(op); (op) = (op2); Py_DECREF(_py_tmp); } while (0) </code></pre> <p>可以看出, <strong>Py_SETREF</strong> 是用这个self对象替换了 <em>pfunc指向的对象了, 而</em> pfunc在上面已经提及到了, 就是当时压入运行时栈的函数对象. 除了这几步, 还有更重要的就是, na 和 n 都分别自增1</p> <p>看回上面的 a.f(), 咱们可以知道, 它是不需要参数的, 所以理论上 na,nk和n都是0, 但是因为f是method(方法), 经过上面一系列操作, 它将会传入一个self,而na也会变成1, 又因为*pfunc已经被替换成self, 相应代码:</p> <pre> <code class="language-python">if (PyFunction_Check(func)) x = fast_function(func, pp_stack, n, na, nk); else x = do_call(func, pp_stack, na, nk); </code></pre> <p>所以它不再进入function的寻常路了, 而是走 <strong>do_call</strong> , 然后就开始真正的调用;</p> <p>其实这个涉及到Python调用函数的整个过程, 因为比较复杂, 后期找个时间专门谈谈这个</p> <p>聊到这里, 我们已经大致清楚, 一个 <strong>method(方法)</strong> 在调用时所发生的过程.明白了函数和方法的本质区别, 那么回到主题上 来说下 <strong>Unbound</strong> 和 <strong>Bound</strong> , 其实这两者差别也不大. 从上面我们得知, 一个方法的创建, 是需要self, 而调用时, 也会使用self,而只有实例化对象, 才有这个self, class是没有的, 所以像下面的执行, 是失败的额</p> <pre> <code class="language-python">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) </code></pre> <p>错误已经很明显了: <strong>函数未绑定, 必须要将A的实例作为第一个参数</strong></p> <p>既然它要求第一个参数是 A的实例对象, 那我们就试下修改代码:</p> <pre> <code class="language-python">class A(object): def f(self): return 1 a = A() print '#### 各自方法等效调用 ####' print '## 类方法 %s' % A.f(a) #传入A的实例a print '## 实例方法 %s' % a.f() ## 结果 ## #### 各自方法等效调用 #### ## 类方法 1 ## 实例方法 1 </code></pre> <p>可以看出来, <strong>Bound</strong> 和 <strong>Unbound</strong> 判断的依据就是, 当方法真正执行时, 有没有传入实例, A.f(a) 和 a.f() 用法的区别只是在于, 第一种需要人为传入实例才能调用, 而第二种, 是虚拟机帮我们做好了传入实例的动作, 不用我们那么麻烦而已, 两种方法 <strong>本质上是等价的。</strong></p> <p> </p> <p>来自:http://python.jobbole.com/87698/</p> <p> </p>