使用 C API 和 `object` 的 `tp_basicsize` 从任意 Python class 派生?

Deriving from arbitrary Python class with C API and `tp_basicsize` of `object`?

我正在尝试定义一个函数,它将使用 C API 创建一个 Python class,派生自任意 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;
}

问题是当 basebuiltins.object 时,此代码会崩溃。在那种情况下 tp_basesize 不计算通常存储 __dict__ 的字段,并且 my_ptr_offset 最终指向该字段,最终导致它被 [=25 的消费者覆盖=].

任何从 object 派生的简单 Python class 都没有这个问题。例如:

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 也有类似的问题。

现在我只是手动检查 exceptionobject 并将 2x sizeof(void*) 添加到 tp_basesize,这似乎有效。但我想了解如何正确处理该布局。

我想你要的信息在基地的tp_dictoffset里。如果将其设置为 0,则基数没有 __dict__,其他任何东西都有。

我有点不清楚你是如何创建你的类型的,但至少是通过调用 PyType_Type(在 [=26= 中编写 class X: 时内部使用的方法]) 除非定义 __slots__ 否则会添加一个 dict - 听起来这既是你想要发生的事情也是正在发生的事情。这在我链接的文档部分的 "Inheritance" 下有详细说明。

因此,如果 tp_dictoffset == 0(假设您没有定义 __slots__),则添加 sizeof(PyObject*) 以说明隐式添加的字典。