如何在Python C-API中dynamic创build派生types
假设我们的Noddy
types是在编写Python的C扩展模块的教程中定义的。 现在我们要创build一个派生types,只覆盖Noddy
的__new__()
方法。
目前我使用下面的方法(为了可读性剥离错误检查):
PyTypeObject *BrownNoddyType = (PyTypeObject *)PyType_Type.tp_alloc(&PyType_Type, 0); BrownNoddyType->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; BrownNoddyType->tp_name = "noddy.BrownNoddy"; BrownNoddyType->tp_doc = "BrownNoddy objects"; BrownNoddyType->tp_base = &NoddyType; BrownNoddyType->tp_new = BrownNoddy_new; PyType_Ready(BrownNoddyType);
这工作,但我不知道是否是正确的做法。 我会期望我也必须设置Py_TPFLAGS_HEAPTYPE
标志,因为我dynamic地在堆上分配types对象,但这样做会导致解释器中的段错误。
我也想过使用PyObject_Call()
或类似的方法显式调用type()
,但我放弃了这个想法。 我需要将函数BrownNoddy_new()
包装在一个Python函数对象中,并创build一个将__new__
映射到这个函数对象的字典,这似乎很愚蠢。
什么是最好的方式去做这件事? 我的方法是否正确? 有没有我错过的界面function?
更新
在python-dev邮件列表(1) (2)的相关主题上有两个线程。 从这些线程和几个实验我推断,我不应该设置Py_TPFLAGS_HEAPTYPE
除非types是通过调用type()
分配的。 在这些线程中有不同的build议是手动分配types还是调用type()
更好。 如果我知道推荐的方法来包装应该在tp_new
插槽中执行的C函数,我会对后者感到满意。 对于常规的方法,这一步将很容易 – 我可以使用PyDescr_NewMethod()
来获得合适的包装对象。 我不知道如何为我的__new__()
方法创build这样一个包装器对象 – 也许我需要未PyCFunction_New()
的函数PyCFunction_New()
来创build这样一个包装器对象。
当我修改一个与Python 3兼容的扩展时遇到了同样的问题,当我试图解决这个问题时,发现了这个页面。
我最终通过阅读Python解释器的源代码PEP 0384和C-API的文档来解决这个问题。
设置Py_TPFLAGS_HEAPTYPE
标志告诉解释器将PyHeapTypeObject
改写为PyHeapTypeObject
,它包含必须同时分配的其他成员。 在某些时候,解释器会试图引用这些额外的成员,如果你将它们留给未分配的地址,将导致段错误。
Python 3.2引入了C结构PyType_Slot
和PyType_Spec
以及C函数PyType_FromSpec
,可以简化dynamictypes的创build。 简而言之,您使用PyType_Slot
和PyType_Spec
来指定tp_*
成员,然后调用PyType_FromSpec
来执行分配和初始化内存的肮脏工作。
从PEP 0384,我们有:
typedef struct{ int slot; /* slot id, see below */ void *pfunc; /* function pointer */ } PyType_Slot; typedef struct{ const char* name; int basicsize; int itemsize; int flags; PyType_Slot *slots; /* terminated by slot==0. */ } PyType_Spec; PyObject* PyType_FromSpec(PyType_Spec*);
(以上不是PEP 0384的文字副本,它也包含const char *doc
作为PyType_Spec
的成员,但该成员没有出现在源代码中)。
要在原始示例中使用它们,假设我们有一个C结构BrownNoddy
,它扩展了基类Noddy
的C结构。 那么我们会有:
PyType_Slot slots[] = { { Py_tp_doc, "BrownNoddy objects" }, { Py_tp_base, &NoddyType }, { Py_tp_new, BrownNoddy_new }, { 0 }, }; PyType_Spec spec = { "noddy.BrownNoddy", sizeof(BrownNoddy), 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots }; PyTypeObject *BrownNoddyType = (PyTypeObject *)PyType_FromSpec(&spec);
这应该在原始代码中做所有事情,包括调用PyType_Ready
,以及创build一个dynamictypes所需的东西,包括设置Py_TPFLAGS_HEAPTYPE
,以及为PyHeapTypeObject
分配和初始化额外的内存。
我希望这是有帮助的。
我很抱歉,如果这个答案是可怕的,但你可以在PythonQt中find这个想法的实现 ,特别是我认为下面的文件可能是有用的参考:
- PythonQtClassInfo.cpp
- PythonQtClassInfo.h
- PythonQtClassWrapper.cpp
- PythonQtClassWrapper.h
来自PythonQtClassWrapper_init的这个片断在我看来有些有趣:
static int PythonQtClassWrapper_init(PythonQtClassWrapper* self, PyObject* args, PyObject* kwds) { // call the default type init if (PyType_Type.tp_init((PyObject *)self, args, kwds) < 0) { return -1; } // if we have no CPP class information, try our base class if (!self->classInfo()) { PyTypeObject* superType = ((PyTypeObject *)self)->tp_base; if (!superType || (superType->ob_type != &PythonQtClassWrapper_Type)) { PyErr_Format(PyExc_TypeError, "type %s is not derived from PythonQtClassWrapper", ((PyTypeObject*)self)->tp_name); return -1; } // take the class info from the superType self->_classInfo = ((PythonQtClassWrapper*)superType)->classInfo(); } return 0; }
值得注意的是,PythonQt使用了一个包装器生成器,所以它不完全符合你所要求的,但是我个人认为试图超越vtable并不是最优化的devise。 基本上,Python有许多不同的C ++包装生成器,人们使用它们的原因很多 – 它们被logging下来,在search结果和堆栈溢出中都有例子。 如果你为之前没有人看到过的解决scheme,如果遇到问题就更难debugging。 即使它是封闭的,下一个维护它的人也会挠头,你必须向每个新来的人解释一下。
一旦你得到了一个代码生成器的工作,所有你需要做的就是维护底层的C ++代码,你不必手动更新或修改你的扩展代码。 (这可能不会太远离你所使用的诱人解决scheme)
所提出的解决scheme是打破新引入的PyCapsule提供更多保护 (在按指示使用时)的types安全性的示例。
所以,尽pipe这样做可能不是最好的长期select来实现派生/子类,而是包装代码,让vtable做什么最好,当新的人有问题,你可以指向他在任何 解决scheme 最 适合的文档。
这只是我的意见。 :d
尝试和理解如何做到这一点的一种方法是使用SWIG创build它的一个版本。 看看它产生了什么,看看它是否匹配或以不同的方式完成。 我可以告诉那些一直在写SWIG的人对Python的扩展有深入的理解。 不能伤害看到他们如何做任何事情。 这可能会帮助你理解这个问题。