当前位置: 首页 > 科技观察

Python中的函数和方法与BoundMethod和UnboundMethod

时间:2023-03-20 12:02:57 科技观察

函数和方法的区别随着我们越来越频繁地使用Python,我们不可避免地会接触到类,类属性和方法。但是很多新手,包括我在内,都分不清方法和函数的区别。这次我们就简单的讨论一下。如果有不对的地方还望大神指点!我们先看看这两个定义:函数(function)——一系列返回一些值给调用者的语句。它也可以传递零个或多个参数,这些参数可以在主体的执行中使用。方法(method)——定义在类体内的函数。如果作为该类实例的属性调用,该方法将获取实例对象作为其第一个参数(通常称为self)。从上面可以看出,和其他编程语言一样,Function也包含函数头和函数体,也支持0到n个形参,而Method是基于函数的,多了一层类关系。因为类的这一层,函数和方法是有区别的。这个过程是通过PyMethod_NewPyObject*PyMethod_New(PyObject*func,PyObject*self,PyObject*klass){registerPyMethodObject*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)returnNULL;}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);返回(PyObject*)im;所以本质上,函数和方法的区别是:函数属于FunctionObject,而方法属于PyMethodObject简单看代码:defaa(d,na=None,*kasd,**kassd):passclassA(object):deff(self):return1a=A()print'####各自的方法说明####'print'##函数%s'%aaprint'##类方法%s'%A.fprint'##实例方法%s'%a.f输出结果:####各自方法说明######function##类方法##实例方法>BoundMethod和UnboundMethodMethod又可以分为BoundMethod和UnboundMethod,它们有什么区别呢?不同的是Bound方法多了一个实例绑定过程!a.f是unbound方法,a.f是bound方法,从而验证上面的描述是正确的!看到这里,我们应该会有一个问题:方法绑定确定了,是什么时候发生的?它是怎么发生的?带着这个疑问,我们继续探索。显然,方法绑定必须与类的实例化一起发生。我们都知道在类中定义一个方法,需要显示传入的self参数,因为这个self代表了即将被实例化的对象我们需要dis模块来辅助我们观察绑定过程:[root@iZ23pynfq19Z~]#cat33.pyclassA(object):deff(self):return123a=A()printA.f()printa.f()##命令执行##[root@iZ23pynfq19Z~]#python-mdis33.py10LOAD_CONST0('A')3LOAD_NAME0(object)6BUILD_TUPLE19LOAD_CONST1()12MAKE_FUNCTION015CALL_FUNCTION018BUILD_CLASS19STORE_NAME1(A)422LOAD_NAME1(A)25CALL_FUNCTION028STORE_NAME2(a)531LOAD_NAME1(A)34LOAD_ATTR3(f)37CALL_FUNCTION040PRINT_ITEM41PRINT_NEWLINE642LOAD_NAME2(a)45LOAD_ATTR3(f)48CALL_FUNCTION051PRINT_ITEM52PRINT_NEWLINE53LOAD_CONST2(None)56RETURN_VALUEdis第四列偏移量为代码输出指令,第二列指令为可视指令命令根据参数计算或搜索的结果。我们可以看到第四列和第五列,对应的是:printA.f()和printa.f(),它们是同一个Bytecode,参数对应的name取自所在codeobject中的co_name。因为参数不同,所以分别得到A和a。接下来我们要看看LOAD_ATTR的作用是什么://取自:python2.7/objects/ceval.cTARGET(LOAD_ATTR){w=GETITEM(names,oparg);//取自fv=TOP()co_name;//获取刚才入栈的A/ax=PyObject_GetAttr(v,w);//获取真正的执行函数Py_DECREF(v);SET_TOP(x);if(x!=NULL)DISPATCH();break;}通过SET_TOP,我们真正需要执行的函数已经被压入运行时栈,接下来就是通过CALL_FUNCTION来调用这个函数对象,继续看细节Process://takenfrom:python2.7/objects/ceval.cTARGET(CALL_FUNCTION){PyObject**sp;PCALL(PCALL_ALL);sp=stack_pointer;#ifdefWITH_TSCx=call_function(&sp,oparg,&intr0,&intr1);#elsex=call_function(&sp,oparg);//详情请往下看#endifstack_pointer=sp;PUSH(x);if(x!=NULL)DISPATCH();break;}staticPyObject*call_function(PyObject***pp_stack,intoparg){intna=oparg&0xff;//位置参数个数intnk=(oparg>>8)&0xff;//关键位置参数个数intn=na+2*nk;//总个数和PyObject**pfunc=(*pp_stack)-n-1;//当前栈位置-参数个数,得到函数对象PyObject*func=*pfunc;PyObject*x,*w;...//省略前面的细节,看看关键调用if(PyMethod_Check(func)&&PyMethod_GET_SELF(func)!=NULL){/*optimizeaccesstoboundmethods*/PyObject*self=PyMethod_GET_SELF(func);PCALL(PCALL_METHOD);PCALL(PCALL_BOUND_METHOD);Py_INCREF(PyFUNCTION(self);Method_func=Py_INCREF(func);Py_SETREF(*pfunc,self);na++;n++;}elsePy_INCREF(func);READ_TIMESTAMP(*pintr0);if(PyFunction_Check(func))x=fast_function(func,pp_stack,n,na,nk);elsex=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_SETREFisdefined如下#definePy_SETREF(op,op2)do{PyObject*_py_tmp=(PyObject*)(op);(op)=(op2);Py_DECREF(_py_tmp);}while(0)可以看出Py_SETREF被替换为thisselfobject是pfunc指向的对象,pfunc上面已经提到过,就是被push到ru中的函数对象当时的ntime堆栈。除了这些步骤之外,更重要的是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);elsex=do_call(func,pp_stack,na,nk);所以不再进入通常的function方式,而是取do_call,然后开始真正的调用;其实这个涉及到Python调用函数的全过程,因为比较复杂,以后找个时间再说说到这里,我们已经大致了解了一个方法(method)被调用时发生的过程。了解了函数和方法的本质区别之后,我们回归正题,说说Unbound和Bound。事实上,两者之间的区别也不大。从上面我们知道方法的创建需要self,调用的时候也要用到self,而且只有实例化的对象才有这个self,class没有,所以像下面这样的执行是FailedamountclassA(object):deff(self):return1a=A()print'####各自方法等价调用####'print'##类方法%s'%A.f()print'##实例方法%s'%a.f()##输出结果######各个方法等效调用####Traceback(mostrecentcalllast):File"C:/Users/Administrator/ZGZN_Admin/ZGZN_Admin/1.py",line20,inprint'##classmethod%s'%A.f()TypeError:unboundmethodf()mustbecalledwithAinstanceasfirstargument(gotnothinginstead)错误已经很明显了:函数未绑定,必须使用A的实例作为第一个参数由于它要求第一个参数是A的实例对象,所以我们尝试修改代码:classA(object):deff(self):return1a=A()print'####每个方法的等价调用####'print'##类方法%s'%A.f(a)#传入A的实例print'##Instancemethod%s'%a.f()##Result######各个方法是等价的调用######classmethod1##instancemethod1可以看出,基础为Bound和Unbound的判断是,方法真正执行的时候,是否传入了实例,A.f(a)和a.f()的用法区别只是第一个方法需要手动传入调用,而第二种方法是虚拟机帮我们做传入实例的动作,就不用我们那么麻烦了。这两种方法本质上是等价的