zoukankan      html  css  js  c++  java
  • python中class成员函数使用的descriptor原因

    一、简单的例子

    tsecer@harry: cat classdump.py
    class tsecer(object):
    x = 1
    def dump(self):
    print(self.x)
    t=tsecer()
    d = t.dump
    d()
    tsecer.x=2
    d()
    tsecer@harry: ../../Python-3.6.0/python classdump.py
    1
    2
    tsecer@harry: ../../Python-3.6.0/python -m dis classdump.py
    1 0 LOAD_BUILD_CLASS
    2 LOAD_CONST 0 (<code object tsecer at 0x7f70eae1fc40, file "classdump.py", line 1>)
    4 LOAD_CONST 1 ('tsecer')
    6 MAKE_FUNCTION 0
    8 LOAD_CONST 1 ('tsecer')
    10 LOAD_NAME 0 (object)
    12 CALL_FUNCTION 3
    14 STORE_NAME 1 (tsecer)

    5 16 LOAD_NAME 1 (tsecer)
    18 CALL_FUNCTION 0
    20 STORE_NAME 2 (t)

    6 22 LOAD_NAME 2 (t)
    24 LOAD_ATTR 3 (dump)
    26 STORE_NAME 4 (d)

    7 28 LOAD_NAME 4 (d)
    30 CALL_FUNCTION 0
    32 POP_TOP

    8 34 LOAD_CONST 2 (2)
    36 LOAD_NAME 1 (tsecer)
    38 STORE_ATTR 5 (x)

    9 40 LOAD_NAME 4 (d)
    42 CALL_FUNCTION 0
    44 POP_TOP
    46 LOAD_CONST 3 (None)
    48 RETURN_VALUE

    二、类结构的解析

    /* AC: cannot convert yet, waiting for *args support */
    static PyObject *
    builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
    {
    ……
    if (meta == NULL) {
    /* if there are no bases, use type: */
    if (PyTuple_GET_SIZE(bases) == 0) {
    meta = (PyObject *) (&PyType_Type);
    }
    /* else get the type of the first base */
    else {
    PyObject *base0 = PyTuple_GET_ITEM(bases, 0);
    meta = (PyObject *) (base0->ob_type);
    }
    Py_INCREF(meta);
    isclass = 1; /* meta is really a class */
    }
    ……
    if (cell != NULL) {
    PyObject *margs[3] = {name, bases, ns};
    cls = _PyObject_FastCallDict(meta, margs, 3, mkw);
    ……
    }
    通常情况下,这里的meta对应的是PyType_Type,在该类型的tp_new接口定义为type_new。该函数使用类中所有语句获得该函数的字典,并把这个字典作为一个class类型的字典
    static PyObject *
    type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
    {
    ……
    /* Check arguments: (name, bases, dict) */
    if (!PyArg_ParseTuple(args, "UO!O!:type.__new__", &name, &PyTuple_Type,
    &bases, &PyDict_Type, &orig_dict))
    ……
    }

    三、method方法的定义

    在类中method解析的时候,我们看到的是一个类型的声明,所以通过由PyDescr_NewMethod函数生成的PyMethodDescrObject类型对象表示。这个类型只是描述了method的各种属性,并且根据该类型可以生成多个具体对象。
    int
    PyType_Ready(PyTypeObject *type)
    {
    ……
    if (type->tp_methods != NULL) {
    if (add_methods(type, type->tp_methods) < 0)
    goto error;
    }
    ……
    }

    /* Add the methods from tp_methods to the __dict__ in a type object */
    static int
    add_methods(PyTypeObject *type, PyMethodDef *meth)
    {
    PyObject *dict = type->tp_dict;

    for (; meth->ml_name != NULL; meth++) {
    PyObject *descr;
    int err;
    if (PyDict_GetItemString(dict, meth->ml_name) &&
    !(meth->ml_flags & METH_COEXIST))
    continue;
    if (meth->ml_flags & METH_CLASS) {
    if (meth->ml_flags & METH_STATIC) {
    PyErr_SetString(PyExc_ValueError,
    "method cannot be both class and static");
    return -1;
    }
    descr = PyDescr_NewClassMethod(type, meth);
    }
    else if (meth->ml_flags & METH_STATIC) {
    PyObject *cfunc = PyCFunction_NewEx(meth, (PyObject*)type, NULL);
    if (cfunc == NULL)
    return -1;
    descr = PyStaticMethod_New(cfunc);
    Py_DECREF(cfunc);
    }
    else {
    descr = PyDescr_NewMethod(type, meth);
    }
    if (descr == NULL)
    return -1;
    err = PyDict_SetItemString(dict, meth->ml_name, descr);
    Py_DECREF(descr);
    if (err < 0)
    return -1;
    }
    return 0;
    }

    四、method方法的调用

    在开始例子中执行d = t.dump时,对应的虚拟机指令是LOAD_ATTR。首先查找的是类型是否有该属性,如果有并且该类型实现了tp_descr_get接口,就调用该接口来返回属性,否则直接返回对象中的该属性。
    static PyObject *
    method_getattro(PyObject *obj, PyObject *name)
    {
    PyMethodObject *im = (PyMethodObject *)obj;
    PyTypeObject *tp = obj->ob_type;
    PyObject *descr = NULL;

    {
    if (tp->tp_dict == NULL) {
    if (PyType_Ready(tp) < 0)
    return NULL;
    }
    descr = _PyType_Lookup(tp, name);
    }

    if (descr != NULL) {
    descrgetfunc f = TP_DESCR_GET(descr->ob_type);
    if (f != NULL)
    return f(descr, obj, (PyObject *)obj->ob_type);
    else {
    Py_INCREF(descr);
    return descr;
    }
    }

    return PyObject_GetAttr(im->im_func, name);
    }

    五、为什么要这么做

    再回到开始的例子
    class tsecer(object):
    x = 1
    def dump(self):
    print(self.x)
    t=tsecer()
    d = t.dump
    d()
    tsecer.x=2
    d()
    在类型tsecer内,dump只是一个类型,这个类型只有在有具体的对象指针之后才有意义。比方说
    t=tsecer()
    s=tsecer()
    td=t.dump
    sd=s.dump
    此时td和sd由于它们绑定的tsecer对象不同,所以返回的dump实例也不相同,所以这个具体的绑定就要推迟到属性查找的时候,也就是t.dump被执行的时候,这个时候调用的descriptor就可以相当于是一个钩子函数,它可以“执行动作”,最关键的就是为method绑定具体对象。
    和dump函数对应的,x是类的共享变量,不需要绑定对象,从tsecer中查找到直接返回即可,所以也不需要这个中间的descriptor钩子函数。

  • 相关阅读:
    Interview with BOA
    Java Main Differences between HashMap HashTable and ConcurrentHashMap
    Java Main Differences between Java and C++
    LeetCode 33. Search in Rotated Sorted Array
    LeetCode 154. Find Minimum in Rotated Sorted Array II
    LeetCode 153. Find Minimum in Rotated Sorted Array
    LeetCode 75. Sort Colors
    LeetCode 31. Next Permutation
    LeetCode 60. Permutation Sequence
    LeetCode 216. Combination Sum III
  • 原文地址:https://www.cnblogs.com/tsecer/p/10407961.html
Copyright © 2011-2022 走看看