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>