Python内核阅读(十六): 类机制 (下)

Python 2017-08-21

起步

上一章讨论类的初始化和继承的处理过程, 这节单将class的实现, 没有关于继承多态的问题. 本章讨论类的定义, 类的构造函数, 对象的实例化, 成员函数的调用等.

自定义 class

用户自定的class其实可以理解为自定义type类型.

class A(object):
    name = 'python'
    def __init__(self):
        print('A->__init__')
    def f(self):
        print('A->f')
    def g(self, a_value):
        self.value = a_value
        print(self.value)

a = A()
a.f()
a.g(10)

对于一个py源码文件, 经过编译后会得到一个对应的 PyCodeObject 对象. 前面说的一个函数也会对应一个 PyCodeObject . 这边再加一层, 每个class也会有对应的 PyCodeObject . 因此上面的python一共会产生 5 个code 对象.

创建 class 对象

在python中. class被设计为type, class也是一个函数, 因此 a = A() 这样的其实也是指令 CALL_FUNCTION 来执行. 而在创建A函数时, MAKE_FUNCTION A 需要获取到class的元信息, 如class的名称, 它拥有的属性,方法, 实例化要申请多大的内存空间等.对于例子中的A, 符号f, g 都对应一个函数.

先来看看表示.py文件的PyCodeObject 中的常量表和符号表:

co_consts : (<code object A >, 'A', 10, None)
co_names : ('object', 'A', 'a', 'f', 'g')

class A 可以理解就是将A的code赋值给符号表中的A:

class A(object):
 0 LOAD_BUILD_CLASS
 2 LOAD_CONST               0 (<code object A >)
 4 LOAD_CONST               1 ('A')
 6 MAKE_FUNCTION            0
 8 LOAD_CONST               1 ('A')
10 LOAD_NAME                0 (object)
12 CALL_FUNCTION            3
14 STORE_NAME               1 (A)

与一般函数不同, 这里的A的code对象是要用来创建class的, 尽管A的code是先MAKE_FUNCTION 的, 要将函数转化为class,就要 LOAD_BUILD_CLASS :

TARGET(LOAD_BUILD_CLASS) {
    _Py_IDENTIFIER(__build_class__);
    PyObject *bc;
    if (PyDict_CheckExact(f->f_builtins)) {
        bc = _PyDict_GetItemId(f->f_builtins, &PyId___build_class__);
        Py_INCREF(bc);
    }
    else {
        PyObject *build_class_str = _PyUnicode_FromId(&PyId___build_class__);
        if (build_class_str == NULL)
            goto error;
        bc = PyObject_GetItem(f->f_builtins, build_class_str);
    }
    PUSH(bc);
    DISPATCH();
}

这部分就是从python的内置函数中取得 __build_class__ 将其入栈, CALL_FUNCTION 3 的三个参数分别是:object, A, __build_class__. 因此:

class A(object):
    ...

就会被替换成:

A = __build_class__(<fun A>, 'A', object)

build_class就是用来将一个函数对象变成class对象, 其实这个转换就是将本来是闭包结构的函数对象, 将闭包函数的第一个参数self通过某种方法将实例赋值进去. build_class的原型是: __build_class__(func, name, *bases, metaclass=None, **kwds) -> class. 有兴趣可以看看Guido在EPE 3115 中的解释.

因为CALL_FUNCTION, python虚拟机会为此创建一个新的PyFrameObject对象来执行这段字节码序列, 因此对应的字节码序列就换到了class A的body里了:

class A(object):
     0 LOAD_NAME                0 (__name__)
     2 STORE_NAME               1 (__module__)
     4 LOAD_CONST               0 ('A')
     6 STORE_NAME               2 (__qualname__)
name = 'python'
     8 LOAD_CONST               1 ('python')
    10 STORE_NAME               3 (name)

def __init__(self):
    12 LOAD_CONST               2 (<code object __init__ >)
    14 LOAD_CONST               3 ('A.__init__')
    16 MAKE_FUNCTION            0
    18 STORE_NAME               4 (__init__)

def f(self):
    20 LOAD_CONST               4 (<code object f>)
    22 LOAD_CONST               5 ('A.f')
    24 MAKE_FUNCTION            0
    26 STORE_NAME               5 (f)
def g(self, a_value):
    28 LOAD_CONST               6 (<code object g>)
    30 LOAD_CONST               7 ('A.g')
    32 MAKE_FUNCTION            0
    34 STORE_NAME               6 (g)

这段中创建了一个符号标识和三个PyFunctionObject对象. class A 中常量表和符号表为:

co_consts : ('A', 'python', <code object __init__ >, 'A.__init__', <code object f >, 'A.f', <code object g >, 'A.g', None)
co_names : ('__name__', '__module__', '__qualname__', 'name', '__init__', 'f', 'g')

开始的 LOAD_NAMESTORE_NAME 将符号表中 __module__ 和全局名字空间中的 __name__ 关联起来(ps: name的值是'main'), 并放入到 local 名字空间中. 与函数机制不同, 这里的f_locals是个字典. 而函数机制中, 局部变量是以一种位置参数的形式存在运行时栈前面的那段内存中.

接着, 虚拟机执行3个(LOAD_CONST, MAKE_FUNTION, STORE_NAME)指令对. 每个指令序列都会创建与成员函数对应的 PyFunctionObject , 并通过store_name将其存入到local名字空间中.

__build_class__

内置函数 __build_class__ 是用来创建类的函数:

[bltinmondule.c]
static PyMethodDef builtin_methods[] = {
    {"__build_class__", (PyCFunction)builtin___build_class__,METH_FASTCALL | METH_KEYWORDS, build_class_doc},
    ...
};

获得metaclass

static PyObject * builtin___build_class__(PyObject *self, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
{
    PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns;
    PyObject *cls = NULL, *cell = NULL;
    int isclass = 0;   /* initialize to prevent gcc warning */

    func = args[0];  // A 对应的fun对象
    name = args[1]; // class 的名称 A
    bases = _PyStack_AsTupleSlice(args, nargs, 2, nargs);// class A 的基类列表
    if (bases == NULL)
        return NULL;

    if (kwnames == NULL) {  // class A创建时kwnames = NULL
        meta = NULL;
        mkw = NULL;
    }
    else {
        ...
    }
    if (meta == NULL) {
        /* if there are no bases, use type: */
        if (PyTuple_GET_SIZE(bases) == 0) {         // PyTuple_GET_SIZE(bases) = 1
            meta = (PyObject *) (&PyType_Type);
        }
        /* else get the type of the first base */
        else {
            PyObject *base0 = PyTuple_GET_ITEM(bases, 0);   // 获得第一基类 object
            meta = (PyObject *) (base0->ob_type);           // 第一基类的 object.__class__
        }
        Py_INCREF(meta);
        isclass = 1;  // 说明这是一个类
    }

    if (isclass) {
        /* meta is really a class, so check for a more derived
           metaclass, or possible metaclass conflicts: */
        winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta,
                                                        bases); // 其实就是 winner = bases[0].__class__

        if (winner != meta) {
            meta = winner;
        }
    }
    /* else: meta is not a class, so we cannot do the metaclass
       calculation, so we will use the explicitly given object as it is */
    prep = _PyObject_GetAttrId(meta, &PyId___prepare__);
    if (prep == NULL) {
        ...
    }
    else {
        PyObject *pargs[2] = {name, bases};
        ns = _PyObject_FastCallDict(prep, pargs, 2, mkw);
        Py_DECREF(prep);
    }

    cell = PyEval_EvalCodeEx(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns,
                             NULL, 0, NULL, 0, NULL, 0, NULL,
                             PyFunction_GET_CLOSURE(func));
    cls = _PyObject_FastCallDict(meta, margs, 3, mkw);
    ...
    PyCell_Set(cell, cls);
    ...
}

在前面, 虚拟机获得了关于class A的属性和方法, 也就是获得了动态元信息. 会检查class是否指定了 __metaclass__ , 如果有指定, 那么就用用户自己指定的metaclass, 这是在如 class A(object, __metaclass__=B) 时用的, 在我们例子中的 class A(object) kwnames 是为NULL的. A的第一基类是object. 而 object.__class__ = <type 'type'> 所以metaclass 为 <type 'type'> 这个class对象.

调用metaclass

获得了metaclass之后, 关于类型的创建被设置在PyTypeObjecttp_call 中, 所以调用也是 call = meta->ob_type->tp_call;:

[cell.c]
PyObject * _PyObject_FastCallDict(PyObject *callable, PyObject **args, Py_ssize_t nargs,
                       PyObject *kwargs)
{
    ...
    call = callable->ob_type->tp_call;
    result = (*call)(callable, argstuple, kwargs);
    return result;
}

[typeobject.c]
static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    ...
    obj = type->tp_new(type, args, kwds);
    ...
    type = Py_TYPE(obj);
    if (type->tp_init != NULL) {    // 如果定义了 __init__ , 则用它进行初始化
        int res = type->tp_init(obj, args, kwds);
    }
    return obj;
}

关于tp_new的结构比较复杂, 这边就先忽略.

从class到instance对象

从class对象出发, 创建instance对象的工作是佮完成的: a = A() 将经历什么:

a = A()
16 LOAD_NAME                1 (A)
18 CALL_FUNCTION            0
20 STORE_NAME               2 (a)

前面创建的是class对象, A = __build_class__(<fun A>, 'A', object) , 创建后将A存放到local名字空间中. 指令LOAD_NAME 1 (A) 将A压入运行时栈中. 后续的指令的看出用 CALL_FUNCTION 0 指令来创建一个instance. 即 "调用 class 对象将创建 instance 对象" .创建后赋值给a, 然后放入local名字空间中.

在创建 class A 对象时, python虚拟机会调用 PyType_Ready<class 'A'> 进行初始化, 其中一个动作就是继承基类, 所以A.tp_new也就是object.tp_new. 申请内存空间的 A.tp_alloc同理也是object.tp_alloc.

[typeobject.c]
PyTypeObject PyBaseObject_Type = {
    ...
    PyType_GenericAlloc,                        /* tp_alloc */
    object_new,                                 /* tp_new */
    ...
}

static PyObject * object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    ...
    return type->tp_alloc(type, 0);
}

PyType_GenericAlloc 中申请的空间是 A->tp_basicsize + nitems * A->tp_itemsize. 空间申请完毕后对申请的对象进行初始化:

[typeobject.c]
static int slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    _Py_IDENTIFIER(__init__);
    int unbound;
    // 查找是否有定义__init__属性操作
    PyObject *meth = lookup_method(self, &PyId___init__, &unbound);
    PyObject *res;

    if (meth == NULL)
        return -1;
    if (unbound) {
        res = _PyObject_Call_Prepend(meth, self, args, kwds);
    }
    else {
        res = PyObject_Call(meth, args, kwds);
    }

    return 0;
}

slot_tp_init 中, 首先通过 lookup_method 查找class 对象的机器mro列表中属性"init" 对应的操作, 有则调用它. 如果没有重写, 那么最终的结果将是调用 object_init , 这个object_init函数中, 什么也没做. 直接返回.

小结下, class对象到instance对象有:

  • instance = class.new(cls, *args, **kwargs)
  • class.init(instance, *args, **kwargs)

访问instance对象中的属性

在例子中, 一共调用了两个A的成员函数, 一个无参, 一个有参. 先看看无参是怎么调用的:

a.f()
22 LOAD_NAME                2 (a)
24 LOAD_ATTR                3 (f)
26 CALL_FUNCTION            0
28 POP_TOP

有个新的指令 LOAD_ATTR :

TARGET(LOAD_ATTR) {
    PyObject *name = GETITEM(names, oparg); // 获取符号表中 'f' PyUnicodeObject类型
    PyObject *owner = TOP();    // instance 对象
    PyObject *res = PyObject_GetAttr(owner, name);
    Py_DECREF(owner);
    SET_TOP(res);
    if (res == NULL)
        goto error;
    DISPATCH();
}

属性访问机制的关键所在是 PyObject_GetAttr 函数:

[object.c]
PyObject * PyObject_GetAttr(PyObject *v, PyObject *name)
{
    PyTypeObject *tp = Py_TYPE(v);

    if (!PyUnicode_Check(name)) {
        PyErr_Format(PyExc_TypeError,
                     "attribute name must be string, not '%.200s'",
                     name->ob_type->tp_name);
        return NULL;
    }
    // 通过 tp_getattro 获得属性对应的对象
    if (tp->tp_getattro != NULL)
        return (*tp->tp_getattro)(v, name);
    // 通过 tp_getattr 获得属性对应的对象
    if (tp->tp_getattr != NULL) {
        const char *name_str = PyUnicode_AsUTF8(name);
        if (name_str == NULL)
            return NULL;
        return (*tp->tp_getattr)(v, (char *)name_str);
    }
    // 属性不存在, 抛出异常
    PyErr_Format(PyExc_AttributeError,
                 "'%.50s' object has no attribute '%U'",
                 tp->tp_name, name);
    return NULL;
}

在class对象中, 有两个与访问属性操作相关的成员: tp_getattrotp_getattr, 其中tp_getattr在python中是不推荐使用的. 它们的差别就是前者的属性名是必须是PyUncodeObject,而后者的属性名必须是c中原生字符串.

在class A中, 它的tp_getattro操作继承自父类PyBaseObject_Type, 因此A.tp_getattro就是object.tp_getattro, 在PyBaseObject_Type的定义为 PyObject_GenericGetAttr :

[object.c]
PyObject * PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
    return _PyObject_GenericGetAttrWithDict(obj, name, NULL);
}

PyObject * _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
{
    PyTypeObject *tp = Py_TYPE(obj);
    PyObject *descr = NULL;
    PyObject *res = NULL;
    descrgetfunc f;
    Py_ssize_t dictoffset;
    PyObject **dictptr;

    if (!PyUnicode_Check(name)){
        PyErr_Format(PyExc_TypeError,
                     "attribute name must be string, not '%.200s'",
                     name->ob_type->tp_name);
        return NULL;
    }
    Py_INCREF(name);

    if (tp->tp_dict == NULL) {
        if (PyType_Ready(tp) < 0)
            goto done;
    }

    descr = _PyType_Lookup(tp, name);   // 尝试从mro列表中获取'f' 
                                        // 等价于 descr = A.f if hasattr(A, 'f') else NULL

    f = NULL;
    if (descr != NULL) {
        Py_INCREF(descr);
        f = descr->ob_type->tp_descr_get;   // // f = descr.__class__.__get__ 
        if (f != NULL && PyDescr_IsData(descr)) {// 如果 descr 同时定义了__get__ 和 __set__
            res = f(descr, obj, (PyObject *)obj->ob_type);// type.__get__(descriptor, a, A) 的执行结果
            goto done;
        }
    }
    // 如果 type 没有同时定义__get__ 和 __set__

    if (dict == NULL) {
        /* Inline _PyObject_GetDictPtr */
        dictoffset = tp->tp_dictoffset;
        if (dictoffset != 0) {  // 说明 A 继承自变长对象,对 dictoffset 做些调整
            if (dictoffset < 0) {
                Py_ssize_t tsize;
                size_t size;

                tsize = ((PyVarObject *)obj)->ob_size;
                if (tsize < 0)
                    tsize = -tsize;
                size = _PyObject_VAR_SIZE(tp, tsize);
                assert(size <= PY_SSIZE_T_MAX);

                dictoffset += (Py_ssize_t)size;
                assert(dictoffset > 0);
                assert(dictoffset % SIZEOF_VOID_P == 0);
            }
            dictptr = (PyObject **) ((char *)obj + dictoffset);
            dict = *dictptr;    // 获得instance的__dict__
        }
    }
    if (dict != NULL) {
        Py_INCREF(dict);
        res = PyDict_GetItem(dict, name);
        if (res != NULL) {
            Py_INCREF(res);
            Py_DECREF(dict);
            goto done;
        }
        Py_DECREF(dict);
    }

    if (f != NULL) {    // 在 a.__dict__ 中没有找到
        res = f(descr, obj, (PyObject *)Py_TYPE(obj));
        goto done;
    }

    if (descr != NULL) {
        res = descr;    // 返回descr
        descr = NULL;
        goto done;
    }

    PyErr_Format(PyExc_AttributeError,
                 "'%.50s' object has no attribute '%U'",
                 tp->tp_name, name);
  done:
    Py_XDECREF(descr);
    Py_DECREF(name);
    return res;
}

可见, descr 可以分为两种:

  • 同时定义了 __get____set__ 的desrc
  • 只定义了 __get__ 的desrc

在python对instance对象作出属性的选择时, intance属性优先于class属性的. 但这有例外, 如果在class发现了同名的且同时定义了 __get____set__ 的desrc, 那么该desrc会优先于instance属性. 很乱有没有.

instance 对象中的 dict

在访问属性中, 有获得instance的 dict. 注意前面说过, 在PyType_Ready 中, 会对type的 tp_dict 进行填充. 但那是类的dict. 实例也有自己的dict. 这部分用来存放实例的动态属性.

>>> class A(object):
...     pass
...
>>> a = A()
>>> a.__dict__
{}
>>> a.name = 'python'
>>> a.name
'python'
>>> a.__dict__
{'name': 'python'}

我们可以动态的实例 a 添加属性. 这段代码只是想说明这一点而已.

填充 tp_dict 目的是将操作名与对应的一个个descriptor关联起来. 接下来要看看descriptor在python的类机制中起怎样的作用.

descriptor

在python虚拟机对class对象或instance对象进行属性访问时, descriptor 对这行为有重大影响. 一般而言, python的一个对象obj. 如果obj.__class__ 中存在get, set, delete 三个操作时,那么这个obj可以成为python一个descriptor. 在slotdefs中, 会看到这三个对应的操作.

[typeobject.c]
static slotdef slotdefs[] = {
    ...
    TPSLOT("__get__", tp_descr_get, slot_tp_descr_get, ...),
    TPSLOT("__set__", tp_descr_set, slot_tp_descr_set, ...),
    TPSLOT("__delete__", tp_descr_set, slot_tp_descr_set, ...),
    ...
};

在python虚拟机访问instance对象时, descriptor的一个作用是影响Python对属性的选择. 以例子中的 a.f() 为例, python会首先

  • 步骤一:尝试从A.mro中获取他们的f属性. 如果找到并且该属性的 ob_type 定义了 __get____set__ , 那么这个属性就会被虚拟机选中. 结束. 否则进入步骤二.
  • 步骤二:中没有找到属性,则从a.dict中获取f属性, 依然没找到进入步骤三
  • 步骤三:从A.f.get中获取

例1:

>>> class X(list):
...     def __get__(self, instance, owner):
...         return 'X.__get'
...
>>> class Y(object):
...     value = X()
...
>>> y = Y()
>>> y.value
'X.__get'
>>> type(y.value)
<class 'str'>

例2:

>>> class X(list):
...     def __get__(self, instance, owner):
...         return 'X.__get'
...
>>> class Y(object):
...     value = X()
...
>>> y = Y()
>>> y.value = 1
>>> y.__dict__['value']
1
>>> type(y.__class__.__dict__['value'])
<class '__main__.X'>
>>> y.value
1

例3:

"""
同时定义了 `__get__` 和 `__set__` 的desrc高于instance的属性
"""
>>> class X(list):
...     def __get__(self, instance, owner):
...         return 'X.__get'
...     def __set__(self, instance, value):
...         print('X.__set')
...         self.append(value)
...
>>> class Y(object):
...     value = X()
...
>>> y = Y()
>>> y.value = 1
X.__set
>>> y.__dict__['value']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'value'
>>> y.__class__.__dict__['value']
[1]
>>> y.value
'X.__get'

函数变身

以起步中的例子,a.f中指令 LOAD_ATTR 最后找到就是 A.f 这个 PyFunctionObject 对象, 接下来就是调用它:

a.f()
22 LOAD_NAME                2 (a)
24 LOAD_ATTR                3 (f)
26 CALL_FUNCTION            0
28 POP_TOP

CALL_FUNCTION 指令的参数是0, 那么就奇怪了, A.f 函数是有一个参数self的. 这个遗失的参数是在什么地方呢. 因此有一个假设, LOAD_ATTR 访问f属性时, 就让f包含了 self 参数.

因此需要再回头看看f属性的获取, 首先 A.f 载入的结果是从A.f.get中获取, 而 __get__ 的操作是 PyFunction_Type 中 tp_descr_get域 设置为 &func_descr_get. 因此 A.f.__get__ 就是 func_descr_get(A.f, a, A) :

[funcobject.c]
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将函数A.f对应的PyFunctionObject进行了包装, 通过 PyMethod_New 在fun函数基础上创建一个新的对象:

[classobject.c]
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;     // 寄放self
    _PyObject_GC_TRACK(im);
    return (PyObject *)im;
}

原来 PyMethodObject 对象已经self好好保存了. im = free_list; 说明采用了缓冲池技术, 和在说list对象是, 缓冲池有80个list对象用来复用. 现在看看 PyMethodObject :

[classobject.h]
typedef struct {
    PyObject_HEAD
    PyObject *im_func;   // 可调用的PyFunctionObject对象
    PyObject *im_self;   // self参数, 即 instance对象
    PyObject *im_weakreflist; // 弱引用列表, 与垃圾回收有关
} PyMethodObject;

PyMethodObject 中将PyFunctionObject和instance对象进行绑定, 绑定后和绑定前为:

>>> a.__class__.__dict__['f']
<function A.f at 0x000001BD6A5BA620>
>>> a.f
<bound method A.f of <__main__.A object at 0x000001BD6A3FB908>>

无参函数调用

在LOAD_ATTR指令之后, 开始执行 CALL_FUNCTION 0 函数调用的动作. 对于 PyMethodObject 对象, 那么它的处理方式就是:

[ceval.c]
Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION
call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames)
{
    PyObject **pfunc = (*pp_stack) - oparg - 1;
    PyObject *func = *pfunc;
    PyObject *x, *w;
    Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
    Py_ssize_t nargs = oparg - nkwargs;
    PyObject **stack = (*pp_stack) - nargs - nkwargs;

    if (PyCFunction_Check(func)) {
        ...
    }else if (Py_TYPE(func) == &PyMethodDescr_Type) {
        ...
    }else {
        if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
            PyObject *self = PyMethod_GET_SELF(func);// 抽取出self
            Py_INCREF(self);
            func = PyMethod_GET_FUNCTION(func);
            Py_INCREF(func);
            Py_SETREF(*pfunc, self);    // self入栈
            nargs++;    // 参数数量+1
            stack--;
        }

        if (PyFunction_Check(func)) {
            x = _PyFunction_FastCallKeywords(func, stack, nargs, kwnames);
        }
        else {
            x = _PyObject_FastCallKeywords(func, stack, nargs, kwnames);
        }
    }
    ...
}

调用成员函数f时, 显式传入参数个数为0, 但虚拟机判断它是PyMethodObject对象时, 会将参数self取出. 使得 x = _PyFunction_FastCallKeywords(func, stack, nargs, kwnames); 中可以获得所需的参数.

带参数的成员函数调用在指令上几乎一致, 就没什么好分析了.

bound method 与 unbound method

python中, 当对作为属性的函数进行引用时, 会有两种形式, 一种是 Bound Method ,这种形式是通过类的实例对象进行属性引用(如 a.f). 而另一种则是通过类进行属性引用, 成为 Unbound Method(如A.f). A.f是PyFunctionObject对象; 而 a.f 则是 PyMethodObject 对象, 这种对象对函数与实例对象进行了绑定.

所以, 在对unbound method进行调用时, 我们必须显式的提供一个instance对象作为函数的第一个位置参数.

静态方法 staticmethod

如果一个类的成员方法通过 staticmethod 进行修饰, 那么这个方法会变成静态方法:

class B(object):
    @staticmethod
    def fun(value):
        print(value)
b = B()
print(b.fun)

这种方法修饰后, fun 就变成了静态方法, 即使通过 b.fun 获得, 它即使上和 B.fun 是等价的, 获取到的是 Unbound Method. 这是怎么回事呢.

我们就只看看虚拟机是如何创建fun函数的:

 8 LOAD_NAME                3 (staticmethod)
10 LOAD_CONST               1 (<code object fun >)
12 LOAD_CONST               2 ('B.fun')
14 MAKE_FUNCTION            0
16 CALL_FUNCTION            1
18 STORE_NAME               4 (fun)

staticmethod 是在builtin名字空间中的对象, 它的类型是 <class 'staticmethod'> ,这个对象会在python启动一并初始化. PyStaticMethod_Type 是它的类型. 通过修饰器fun实际被转化为 fun = staticmethod(B.fun) . staticmethod 是个class, 因此fun是一个 <class 'staticmethod'> 实例化后的instance对象.

既然是个instance对象, 那么它申请空间的 PyObject_GenericAlloc 申请的内存大小是结构体决定的:

[funobject.c]
typedef struct {
    PyObject_HEAD
    PyObject *sm_callable;
    PyObject *sm_dict;
} staticmethod;

申请完后,虚拟机还会调用 __init__ 进行初始化:

[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);
    sm->sm_callable = callable;
    return 0;
}

这是在python内部系统中实现了一个类. PyFunctionObject对象赋值给了 sm_callable . PyStaticMethod_Type 中将域tp_descr_get设置为 sm_descr_get :

[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;
}

这边将PyFunctionObject返回, 也就是在 LOAD_ATTR 指令里, 获取的都是B.fun这个PyFunctionObject对象.

staticmethod只是一个例子, 还有类似property也是这种方式实现的.

总结

本文主要讲述了在虚拟机层次上说明如果创建一个类对象和实例对象. 并讨论实例中访问成员函数的过程.


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

赏个馒头吧