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钩子函数。

  • 相关阅读:
    java实现文件上传下载至ftp服务器
    理解java代理模式
    [置顶] 理解java中的线程池
    wait,notify,非阻塞队列实现生产者,消费者模型
    理解java阻塞队列BlockingQueue
    CentOS下安装配置Nginx
    putty笔记
    CentOs下安装jdk、MySql、Redis笔记
    简述yum和apt-get的区别
    Linux 文件颜色说明
  • 原文地址:https://www.cnblogs.com/tsecer/p/10407961.html
Copyright © 2011-2022 走看看