大师兄的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_New对PyFunctionOjbect进行了一番包装:
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 Method和Unbound 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形式中,PyFunctionObject与instance对象绑定在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对象。
网友评论