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

posted on 2019-02-20 17:31  tsecer  阅读(371)  评论(0编辑  收藏  举报

导航