• Python虚拟机类机制之绑定方法和非绑定方法(七)


    Bound Method和Unbound Method

    在Python中,当对作为属性的函数进行引用时,会有两种形式,一种称为Bound Method,这种形式是通过类的实例对象进行属性引用,而另一种则是通过类进行属性引用,称为Unbound Method。当然,对Bound Method和Unbound Method的调用形式是不同的,其原因可以追溯到LOAD_ATTR中

    demo2.py

    class A(object):
        def g(self, value):
            self.value = value
            print(self.value)
    
    
    a = A()
    A.g(a, 10)
    

      

    其对应的字节码指令序列:

    >>> source = open("demo2.py").read()
    >>> co = compile(source, "demo2.py", "exec")
    >>> import dis
    >>> dis.dis(co)
      1           0 LOAD_CONST               0 ('A')
                  3 LOAD_NAME                0 (object)
                  6 BUILD_TUPLE              1
                  9 LOAD_CONST               1 (<code object A at 0x7f3e67870d50, file "demo2.py", line 1>)
                 12 MAKE_FUNCTION            0
                 15 CALL_FUNCTION            0
                 18 BUILD_CLASS         
                 19 STORE_NAME               1 (A)
    
      7          22 LOAD_NAME                1 (A)
                 25 CALL_FUNCTION            0
                 28 STORE_NAME               2 (a)
    
      8          31 LOAD_NAME                1 (A)
                 34 LOAD_ATTR                3 (g)
                 37 LOAD_NAME                2 (a)
                 40 LOAD_CONST               2 (10)
                 43 CALL_FUNCTION            2
                 46 POP_TOP             
                 47 LOAD_CONST               3 (None)
                 50 RETURN_VALUE   
    

      

    其中的关键就在于"34   LOAD_ATTR   3 (g)"指令,之前我们已经解释过,这里的LOAD_ATTR指令最终会调用type_getattro。在type_getattro中,会在<class A>的tp_dict中发现"g"对应的PyFunctionObject,同样,因为它是一个descriptor,因此也会调用其__get__函数进行转变。在Python虚拟机类机制之从class对象到instance对象(五)这一章剖析a.f时,我们看到这个转变是通过func_descr_get(A.f, a, A)完成的,这里对"g"的转变则是通过func_descr_get(A.g, NULL, A)完成。因此,虽然A.g也得到一个PyMethodObject,但是其中的im_self确实NULL

    在Python中,在对Unbound Method尽心调用时,我们必须显示地提供一个instance对象作为函数的第一个位置参数,因为g无论如何都需要一个self参数。所以才会有A.g(a, 10)这样的形式。而无论是对Unbound Method进行调用,还是对bound Method进行调用,Python虚拟机的动作在本质上都是一样的,都是调用带位置参数的一般函数,区别只在于:当调用Bound Method时,Python虚拟机帮我们完成了PyFunctionObject对象和instance对象的绑定,instance对象将自动成为self参数,而调用Unbound Method时,则没有这个绑定,需要我们自己传入self参数

    下面的代码展示出Bound Method和Unbound Method的不同

    >>> class A(object):
    ...     def f(self):
    ...         pass
    ... 
    >>> a = A()
    >>> bound = a.f
    >>> unbound = A.f
    >>> 
    >>> bound
    <bound method A.f of <__main__.A object at 0x7f3e677c7310>>
    >>> unbound
    <unbound method A.f>
    >>> 
    >>> bound.im_self
    <__main__.A object at 0x7f3e677c7310>
    >>> unbound.im_self
    >>> 
    

      

    在输出Unbound method对象的im_self域时,没有任何东西输出,因为这个域本身就是NULL

    对于成员函数的调用,或者说对于成员函数的绑定过程,有一点值的注意的是,每一次函数调用都会激发一次绑定过程。其原因在于,每次进行属性引用时,都会重新获得属性对应的PyFunctionObject(descritpro),进而创建新的PyMethodObject对象,这一点的开销实在是有些大,下面两段代码展示了不同函数调用方式的绑定次数

    class A(object):
        def f(self):
            pass
    
    
    a = A()
    # 函数绑定100次
    for i in range(100):
        a.f()
    

      

    class A(object):
        def f(self):
            pass
    
    
    a = A()
    func = a.f
    # 函数绑定1次
    for i in range(100):
        func()
    

      

    千变万化的descriptor

    当我们调用instance对象的函数时,最关键的一个动作就是从PyFunctionObject对象向PyMethodObject对象的转变,而这个关键的转变被Python中descriptor概念很自然地融入到Python的类机制中。当我们访问对象中的属性时,由于descriptor的存在,这种转换自然而然地发生了。将这种descriptor的思想推而广之,其实在访问属性时,我们不光能实现从PyFunctionObject到PyMethodObject对象的转变,实际上我们可以做任何事情。在Python内部,也存在着各式各样的descriptor,这些descriptor的存在给Python的类机制赋予了更多的能力。现在,我们来看看Python是如何使用descriptor实现static method的

    demo3.py

    class A(object):
        def g(value):
            print(value)
    
        g = staticmethod(g)
    

      

    demo3.py对应字节码指令序列:

    >>> source = open("demo3.py").read()
    >>> co = compile(source, "demo3.py", "exec")
    >>> co.co_consts
    ('A', <code object A at 0x7f3e677c9198, file "demo3.py", line 1>, None)
    >>> A_co = co.co_consts[1]
    >>> import dis
    >>> dis.dis(A_co)
      1           0 LOAD_NAME                0 (__name__)
                  3 STORE_NAME               1 (__module__)
    
      2           6 LOAD_CONST               0 (<code object g at 0x7f3e677c90a8, file "demo3.py", line 2>)
                  9 MAKE_FUNCTION            0
                 12 STORE_NAME               2 (g)
    
      4          15 LOAD_NAME                3 (staticmethod)
                 18 LOAD_NAME                2 (g)
                 21 CALL_FUNCTION            1
                 24 STORE_NAME               2 (g)
                 27 LOAD_LOCALS         
                 28 RETURN_VALUE    
    

      

    在为A创建动态元信息的过程中,Python虚拟机首先会执行一个def语句,将符号"g"和一个PyFunctionObject对象关联起来,但随后的g = staticmethod(g)则会将"g"与一个staticmethod对象关联起来,从而将属性"g"改造为一个static method

    Python虚拟机在执行"15   LOAD_NAME   3 (staticmethod)"指令时,会从builtin名字空间中获得一个与符号"staticmethod"对应的对象,这个对象在Python启动并初始化时设置,它其实是一个class对象

    >>> staticmethod
    <type 'staticmethod'>
    

      

    所以,执行staticmethod(g)的过程就是一个从class对象创建instance对象的过程,最终将调用PyObject_GenericAlloc申请一段内存,内存空间的大小由staticmethod结构体决定:

    typedef struct {
    	PyObject_HEAD
    	PyObject *sm_callable;
    } staticmethod;
    

      

    申请完内存之后,Python虚拟机还会调用__init__进行初始化操作,<type 'staticmethod'>在Python内部对应的是PyStaticMethod_Type,而其中的tp_init设置为sm_init:

    static int
    sm_init(PyObject *self, PyObject *args, PyObject *kwds)
    {
    	staticmethod *sm = (staticmethod *)self;
    	PyObject *callable;
    
    	if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable))
    		return -1;
    	if (!_PyArg_NoKeywords("staticmethod", kwds))
    		return -1;
    	Py_INCREF(callable);
    	sm->sm_callable = callable;
    	return 0;
    }
    

      

    在初始化时,原来的参数"g"对应的PyFunctionObject被赋给staticmethod对象中的sm_callable。最后,Python虚拟机通过指令"24   STORE_NAME   2 (g)"将符号"g"和这个staticmethod对象关联起来

    在仔细考察PyStaticMethod_Type,发现这里创建的staticmethod对象实际上也是个descriptor,因为在PyStaticMethod_Type中,tp_descr_get指向了sm_descr_get 

    static PyObject * sm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
    {
    	staticmethod *sm = (staticmethod *)self;
    
    	if (sm->sm_callable == NULL) {
    		PyErr_SetString(PyExc_RuntimeError,
    				"uninitialized staticmethod object");
    		return NULL;
    	}
    	Py_INCREF(sm->sm_callable);
    	return sm->sm_callable;
    }
    

      

    当我们访问属性"g"时,不论是通过instance对象访问a.g,还是通过class对象访问A.g,由于"g"是一个位于class对象<class A>的tp_dict中的descriptor,所以会调用其__get__操作(sm_descr_get),直接了当地返回其中保存的最开始与"g"对应的PyFunctionObject对象

  • 相关阅读:
    非常好用的JS滚动代码
    在vs中使用ZedGraph
    通用SQL分页过程
    使用 Request.QueryString 接受参数时,跟编码有关的一些问题
    一个验证Email 的Javascript 函数
    DOS 下将文件名列表写入文件
    .NET 开发人员应该下载的十个必备工具
    中文全文搜索(一)
    关于<![if IE]>
    Lucene 全文索引心得
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9650423.html
Copyright © 2020-2023  润新知