美文网首页
大师兄的Python源码学习笔记(二十八): 虚拟机中的类机制(

大师兄的Python源码学习笔记(二十八): 虚拟机中的类机制(

作者: superkmi | 来源:发表于2021-08-13 09:48 被阅读0次

    大师兄的Python源码学习笔记(二十七): 虚拟机中的类机制(六)
    大师兄的Python源码学习笔记(二十九): 运行环境初始化(一)

    三. 用户自定义Class

    3. 访问instance对象中的属性
    3.6 self和PyMethod对象
    demo.py 
    
    class A(object):
        def __init__(self):
            self.name = 'A'
    
    a = A()
    a.name
    
    • 在demo.py调用class A的self.name时,为什么使用a.name,而不是a.self.name?
    • 观察PyFuncionObject对象的对象类型PyFunction_Type中,__get__对应的tp_descr_get被设置成func_descr_get:
    PyTypeObject PyFunction_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "function",
        sizeof(PyFunctionObject),
        ... ...
        func_descr_get,                             /* tp_descr_get */
        0,                                          /* tp_descr_set */
        ... ...
    };
    
    • 这意味着a.name实际上是一个descriptor
    • 由于a.__dict__中没有"name"存在,所以a.name的返回值会被descriptor改变,其结果将是a.name.__get__,也就是func_descr_get(A.name,a,A):
    Objects\funcobject.c
    
    /* Bind a function to an object */
    static PyObject *
    func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
    {
        if (obj == Py_None || obj == NULL) {
            Py_INCREF(func);
            return func;
        }
        return PyMethod_New(func, obj);
    }
    
    • func_descr_get通过PyMethod_NewPyFunctionOjbect进行了一番包装:
    classobject.c
    
    /* Method objects are used for bound instance methods returned by
       instancename.methodname. ClassName.methodname returns an ordinary
       function.
    */
    
    PyObject *
    PyMethod_New(PyObject *func, PyObject *self)
    {
        PyMethodObject *im;
        if (self == NULL) {
            PyErr_BadInternalCall();
            return NULL;
        }
        im = free_list;
        if (im != NULL) {
            free_list = (PyMethodObject *)(im->im_self);
            (void)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);
        im->im_func = func;
        Py_XINCREF(self);
        im->im_self = self;
        _PyObject_GC_TRACK(im);
        return (PyObject *)im;
    }
    
    • 可以看到PyMethod_New根据PyFunctionOjbect生成了PyMethodObject
    • 代码中出现free_list,说明PyMethodObject采用了缓冲池技术
    Include\classobject.h
    
    typedef struct {
        PyObject_HEAD
        PyObject *im_func;   /* The callable object implementing the method */
        PyObject *im_self;   /* The instance it is bound to */
        PyObject *im_weakreflist; /* List of weak references */
    } PyMethodObject;
    
    • 实际上PyMethod_New*im_self设定为instance对象a。
    • 在Python中,将PyFunctionOjbect对象和一个instance对象通过PyMethodObject对象结合在一起的过程称为成员函数的绑定
    >>>demo.py
    
    >>>class A(object):
    >>>    def name(self):
            ...
    
    >>>a = A()
    >>>print(a.__class__.__dict__['name'])
    >>>print(a.name)
    <function A.name at 0x000002A486CB8040>
    <bound method A.name of <__main__.A object at 0x000002A486CEFCA0>>
    
    3.7 Bound Method和Unbound Method
    • 在Python中,当对作为属性的函数进行引用时,分为Bound MethodUnbound Method两种形式。
    • Bound Method:
    >>>class A(object):
    >>>    def name(self):
    >>>        ...
    
    >>>a = A()
    >>>a.name()
    >>>print(a.name)
    <bound method A.name of <__main__.A object at 0x000002201CECFCA0>>
    
    • Unbound Method:
    >>>class A(object):
    >>>    def name(self):
    >>>        ...
    
    >>>a = A()
    >>>A.name(a)
    >>>print(A.name)
    <function A.name at 0x000001D644A57F70>
    
    • 这两种形式的区别在于,在Bound Method形式中,PyFunctionObjectinstance对象绑定在PyMethodObject中;
    • Unbound Method需要自己传入instance对象,而*im_self也就是python中的__self__也不存在。
    • Bound Method形式中,每次访问都需要绑定一次instance对象;而Bound Method形式只需要绑定一次。
    4. descriptor与static method
    • 在调用instance对象的函数时,关键动作是从PyFunctionObject对象PyMethodObject对象的转变,而这个转变是被descriptor概念自然地融入到Python的类机制中。
    • 在Python内部,存在各种各样的descriptor,这些descriptor的存在给Python的类机制赋予了强大的力量,比如实现static method
    demo.py 
    
    class A(object):
        def g(value):
            print(value)
        g = staticmethod(g)
    
    • 在创建原动态元信息的过程中,虚拟机会先执行def语句,将"g"与PyFunctionObject对象关联;
    • 但随后的g = staticmethod(g)则会将"g"与一个staticmethod对象关联起来,从而将''g"改造成一个static method
      -对应的指令字节码如下:
      4          16 LOAD_NAME                4 (staticmethod)
                 18 LOAD_NAME                3 (g)
                 20 CALL_FUNCTION            1
                 22 STORE_NAME               3 (g)
                 24 LOAD_CONST               3 (None)
                 26 RETURN_VALUE
    
    • 虚拟机在执行4 16 LOAD_NAME 4 (staticmethod)时,会从builtin名字空间中获得一个与符号staticmethod对应的对象。
    >>>print(staticmethod)
    <class 'staticmethod'>
    
    • 它实际上是一个class对象
    Objects\funcobject.c
    
    typedef struct {
        PyObject_HEAD
        PyObject *sm_callable;
        PyObject *sm_dict;
    } staticmethod;
    
    • 所以执行staticmethod(g)的过程就是一个从class对象创建instance对象的过程。
    • 在执行过程中,虚拟机将调用PyType_GenericAlloc申请一段内存:
    Objects\typeobject.c
    
    PyObject *
    PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
    {
        PyObject *obj;
        const size_t size = _PyObject_VAR_SIZE(type, nitems+1);
        /* note that we need to add one, for the sentinel */
    
        if (PyType_IS_GC(type))
            obj = _PyObject_GC_Malloc(size);
        else
            obj = (PyObject *)PyObject_MALLOC(size);
    
        if (obj == NULL)
            return PyErr_NoMemory();
    
        memset(obj, '\0', size);
    
        if (type->tp_flags & Py_TPFLAGS_HEAPTYPE)
            Py_INCREF(type);
    
        if (type->tp_itemsize == 0)
            (void)PyObject_INIT(obj, type);
        else
            (void) PyObject_INIT_VAR((PyVarObject *)obj, type, nitems);
    
        if (PyType_IS_GC(type))
            _PyObject_GC_TRACK(obj);
        return obj;
    }
    
    • 接着,虚拟机还会调用__init__进行初始化动作,class staticmethod对应PystaticMethod_Type类型:
    Objects\funcobject.c
    
    PyTypeObject PyStaticMethod_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "staticmethod",
        sizeof(staticmethod),
        0,
        (destructor)sm_dealloc,                     /* tp_dealloc */
        0,                                          /* tp_print */
        0,                                          /* tp_getattr */
        0,                                          /* tp_setattr */
        0,                                          /* tp_reserved */
        0,                                          /* tp_repr */
        0,                                          /* tp_as_number */
        0,                                          /* tp_as_sequence */
        0,                                          /* tp_as_mapping */
        0,                                          /* tp_hash */
        0,                                          /* tp_call */
        0,                                          /* tp_str */
        0,                                          /* tp_getattro */
        0,                                          /* tp_setattro */
        0,                                          /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
        staticmethod_doc,                           /* tp_doc */
        (traverseproc)sm_traverse,                  /* tp_traverse */
        (inquiry)sm_clear,                          /* tp_clear */
        0,                                          /* tp_richcompare */
        0,                                          /* tp_weaklistoffset */
        0,                                          /* tp_iter */
        0,                                          /* tp_iternext */
        0,                                          /* tp_methods */
        sm_memberlist,              /* tp_members */
        sm_getsetlist,                              /* tp_getset */
        0,                                          /* tp_base */
        0,                                          /* tp_dict */
        sm_descr_get,                               /* tp_descr_get */
        0,                                          /* tp_descr_set */
        offsetof(staticmethod, sm_dict),            /* tp_dictoffset */
        sm_init,                                    /* tp_init */
        PyType_GenericAlloc,                        /* tp_alloc */
        PyType_GenericNew,                          /* tp_new */
        PyObject_GC_Del,                            /* tp_free */
    };
    
    • 其中tp_init对应sm_init:
    Objects\funcobject.c
    
    static int
    sm_init(PyObject *self, PyObject *args, PyObject *kwds)
    {
        staticmethod *sm = (staticmethod *)self;
        PyObject *callable;
    
        if (!_PyArg_NoKeywords("staticmethod", kwds))
            return -1;
        if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable))
            return -1;
        Py_INCREF(callable);
        Py_XSETREF(sm->sm_callable, callable);
        return 0;
    }
    
    • sm_init中可以看出,PyFunctionObject被赋予给了staticmethod对象中的sm_callable
    • 最后,虚拟机通过22 STORE_NAME 3 (g)将"g"与staticmethod对象关联起来。
    • 再看PyStaticMethod_Type中的tp_descr_get指向了sm_descr_get,也是一个descriptor
    Objects\funcobject.c
    
    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;
    }
    
    • 无论通过instance对象class对象访问"g"时,由于"g"是一个位于"class A"的tp_dict中的descriptor,所以会调用其__get__操作,返回其中保存的与"g"对应的PyFunctionObject对象

    相关文章

      网友评论

          本文标题:大师兄的Python源码学习笔记(二十八): 虚拟机中的类机制(

          本文链接:https://www.haomeiwen.com/subject/rsaavltx.html