一、CPython对于类型的管理
PyType_Type / PyInt_Type / PyString_Type ....等这些是“类型对象”,都是PyTypeObject的实例!
PyType_Type的类型指向自己, 而且它还是其它类型的“类型”。
二、问题来了
(1)通过上图可以发现底层是通过*ob_type这样一个指针来完成类型的赋值的,那么问题来了,我如果想定义一个自己的dict,是否可以在PyDict_Type的基础上,把它的*ob_type指向自己的dict类型呢?
PyObject* d = PyDict_New();
d->ob_type = custom_dict;
果不其然,发生了异常!
分析下异常栈:
python27.dll!subtype_dealloc(_object * self) Line 1000 C
type->tp_del(self);
python27.dll!PyObject_ClearWeakRefs(_object * object) Line 925 C
/* Remove the callback-less basic and proxy references */
if (*list != NULL && (*list)->wr_callback == NULL) {
clear_weakref(*list);
if (*list != NULL && (*list)->wr_callback == NULL)
clear_weakref(*list);
}
python27.dll!clear_weakref(_PyWeakReference * self) Line 58 C
if (self->wr_object != Py_None) {
PyWeakReference **list = GET_WEAKREFS_LISTPTR(self->wr_object);
#define GET_WEAKREFS_LISTPTR(o) \
((PyWeakReference **) PyObject_GET_WEAKREFS_LISTPTR(o))
#define PyObject_GET_WEAKREFS_LISTPTR(o) \
((PyObject **) (((char *) (o)) + Py_TYPE(o)->tp_weaklistoffset))
从这里可以看出更改了ob_type后,Py_TYPE(o)获取到的是custom_dict,原因很可能出在custom_dict和原生的dict中tp_weaklistoffset不一致!
# -*-encoding:utf8-*-
class custom_dict(dict):
pass
if __name__ == "__main__":
print custom_dict.__weakrefoffset__ # 128
print dict.__weakrefoffset__ # 0
也就是析构对象进行弱引用清除的时候,本来tp_weaklistoffset应该是0,加上128后,访问到了非法内存引发的异常。
(2)正确的做法
从CPython的源码中找到了答案,像下面这样,通过tp_call创建自定义的类型,注意在CPython层操作对象需要注意对它的引用计数进行修改。
PyObject* pT = PyTuple_New(0);
d = PyObject_Call((PyObject*)custom_dict, pT, nullptr);
Py_DECREF(pT);