子类化其他类型
创建派生自现有类型的新类型是有可能的。 最容易的做法是从内置类型继承,因为扩展可以方便地使用它所需要的 PyTypeObject。 在不同扩展模块之间共享这些 PyTypeObject 结构体则是困难的。
在本例中我们将创建一个继承自内置 list 类型的 SubList 类型。 这个新类型将完全兼容常规列表,但将拥有一个额外的 increment() 方法用于递增内部计数器的值:
>>>
import sublist
s = sublist.SubList(range(3))
s.extend(s)
print(len(s))
6
print(s.increment())
1
print(s.increment())
2
#define PY_SSIZE_T_CLEAN
#include <;Python.h>
typedef struct {
PyListObject list;
int state;
} SubListObject;
static PyObject *
SubList_increment(SubListObject *self, PyObject *unused)
{
self->state++;
return PyLong_FromLong(self->state);
}
static PyMethodDef SubList_methods[] = {
{"increment", (PyCFunction) SubList_increment, METH_NOARGS,
PyDoc_STR("increment state counter")},
{NULL},
};
static int
SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
{
if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
static PyTypeObject SubListType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "sublist.SubList",
.tp_doc = PyDoc_STR("SubList objects"),
.tp_basicsize = sizeof(SubListObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_init = (initproc) SubList_init,
.tp_methods = SubList_methods,
};
static PyModuleDef sublistmodule = {
PyModuleDef_HEAD_INIT,
.m_name = "sublist",
.m_doc = "Example module that creates an extension type.",
.m_size = -1,
};
PyMODINIT_FUNC
PyInit_sublist(void)
{
PyObject *m;
SubListType.tp_base = &PyList_Type;
if (PyType_Ready(&SubListType) < 0)
return NULL;
m = PyModule_Create(&sublistmodule);
if (m == NULL)
return NULL;
Py_INCREF(&SubListType);
if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
Py_DECREF(&SubListType);
Py_DECREF(m);
return NULL;
}
return m;
}
如你所见,此源代码与之前几节中的 Custom 示例非常相似。 我们将逐一分析它们之间的主要区别。
typedef struct {
PyListObject list;
int state;
} SubListObject;
派生类型对象的主要差异在于基类型的对象结构体必须是第一个值。 基类型将已经在其结构体的开头包括了 PyObject_HEAD()。
当一个 Python 对象是 SubList 的实例时,它的 PyObject * 指针可以被安全地强制转换为 PyListObject * 和 SubListObject *:
static int
SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
{
if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
我们可以在上面看到如何将调用传递到基类型的 __init__() 方法。
这个模式在编写具有自定义 tp_new 和 tp_dealloc 成员的类型时很重要。 tp_new 处理句柄不应为具有 tp_alloc 的对象实际分配内存,而是让基类通过调用自己的 tp_new 来处理它。
PyTypeObject 支持用 tp_base 指定类型的实体基类。 由于跨平台编译器问题,你无法使用对 PyList_Type 的引用来直接填充该字段;它应当随后在模块初始化函数中完成:
PyMODINIT_FUNC
PyInit_sublist(void)
{
PyObject* m;
SubListType.tp_base = &PyList_Type;
if (PyType_Ready(&SubListType) < 0)
return NULL;
m = PyModule_Create(&sublistmodule);
if (m == NULL)
return NULL;
Py_INCREF(&SubListType);
if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
Py_DECREF(&SubListType);
Py_DECREF(m);
return NULL;
}
return m;
}
在调用 PyType_Ready() 之前,类型结构体必须已经填充 tp_base 槽位。 当我们从现有类型派生时,它不需要将 tp_alloc 槽位填充为 PyType_GenericNew() -- 来自基类型的分配函数将会被继承。
在那之后,调用 PyType_Ready() 并将类型对象添加到模块中的过程与基本的 Custom 示例是一样的。 |