我正在尝试定义一个函数,该函数将使用C API创建Python类,该函数派生自任意Python类型base
,并且在其原始的类似于C的对象布局中具有一个额外的字段void* my_ptr
。我希望它重用Python的__dict__
功能。
我没有在C语言中执行此操作,因此无法访问C宏。我最初的尝试看起来像这样(伪代码):
PyType Derive(PyType base) {
var newType = new PyType(...);
newType.tp_flags = HeapType | BaseType; // <- this is important,
// one should be able to add new attributes and otherwise use __dict__, subclass, etc
... filling in other things ...
int my_ptr_offset = base.tp_basesize; // put my_ptr immediately after base type data
newType.tp_basesize = my_ptr_offset + sizeof(void*); // instances of new type
// will have instance size = base size + size of my_ptr
...
return newType;
}
问题是base
为builtins.object
时此代码损坏。在那种情况下,tp_basesize
不对字段进行计数,该字段通常会存储__dict__
,并且my_ptr_offset
最终指向该字段,最终导致my_ptr
的使用者将其覆盖。
从object
派生的任何简单Python类都没有这个问题。例如:
class MySimpleClass: pass
在64位计算机上:
PyType mySimpleClass = ...;
PyType object = ...;
mySimpleClass.tp_basesize // <- 32, includes __dict__
object.tp_basesize // <- 16, does not include space for __dict__
我也注意到了builtins.exception
的类似问题。
现在,我只是手动检查exception
和object
,然后将2x sizeof(void*)
添加到tp_basesize
,这似乎可行。但我想了解如何正确处理该布局。
我认为您想要的信息位于基准的tp_dictoffset
中。如果将其设置为0,则该基数没有tp_dictoffset
,其他任何东西都没有。
我对如何创建类型尚不清楚,但是至少要通过调用__dict__
(在Python中编写PyType_Type
时在内部使用的方法)来添加字典,除非class X:
为定义-听起来这既是您要发生的事情,又是正在发生的事情。在我链接的文档的“继承”部分中对此进行了详细说明。
因此,如果__slots__
(并假设您没有定义tp_dictoffset == 0
,则将__slots__
添加到隐式添加的字典中。
没有通过Python C API做到这一点的安全方法。除了通过sizeof(PyObject*)
以外,绝不能以其他任何方式创建堆类型。新堆类型的初始设置非常复杂且与版本有关,其中包含许多未记录的详细信息。 type.__new__
在CPython 3.8.2中超过500行,不包括辅助函数。