如何在Python C-API中动态创建派生类型

Sve*_*ach 72 python derived-class python-2.x python-c-api

假设我们有关于为Python编写C扩展模块Noddy教程中定义的类型.现在我们要创建一个派生类型,只覆盖它的__new__()方法Noddy.

目前我使用以下方法(为了便于阅读而删除了错误检查):

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);
Run Code Online (Sandbox Code Playgroud)

这有效,但我不确定它是不是正确的方法.我本来期望我也必须设置Py_TPFLAGS_HEAPTYPE标志,因为我在堆上动态分配类型对象,但这样做会导致解释器中出现段错误.

我还想过明确地type()使用PyObject_Call()或类似地调用,但我放弃了这个想法.我需要将函数包装BrownNoddy_new()在Python函数对象中并创建一个映射__new__到此函数对象的字典,这看起来很愚蠢.

最好的方法是什么?我的方法是否正确?我错过了接口功能吗?

更新

python-dev邮件列表(1) (2)上的相关主题有两个主题.从这些线程和一些实验中我推断出Py_TPFLAGS_HEAPTYPE除非通过调用分配类型,否则我不应该设置type().这些线程中有不同的建议是手动分配类型还是调用更好type().如果我知道推荐的方法来包装应该插入tp_new插槽的C函数,我会对后者感到满意.对于常规方法,这一步很简单 - 我可以PyDescr_NewMethod()用来获得一个合适的包装器对象.我不知道如何为我的__new__()方法创建这样的包装器对象- 也许我需要未记录的函数PyCFunction_New()来创建这样的包装器对象.

小智 5

我在修改扩展以兼容 Python 3 时遇到了同样的问题,在我试图解决它的时候发现了这个页面。

我最终通过阅读 Python 解释器的源代码、PEP 0384C-API的文档来解决它。

设置Py_TPFLAGS_HEAPTYPE标志告诉解释器重铸您的PyTypeObjectas PyHeapTypeObject,其中包含也必须分配的其他成员。在某些时候,解释器会尝试引用这些额外的成员,如果您将它们保留为未分配状态,则会导致段错误。

Python 3.2 引入了简化动态类型创建的 C 结构PyType_SlotPyType_SpecC 函数PyType_FromSpec。简而言之,您使用PyType_SlotPyType_Spec指定 的tp_*成员,PyTypeObject然后调用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*);
Run Code Online (Sandbox Code Playgroud)

(以上不是来自 PEP 0384 的文字副本,它也包括const char *doc作为 的成员PyType_Spec。但该成员没有出现在源代码中。)

要在原始示例中使用这些,假设我们有一个 C 结构BrownNoddy,它扩展了基类的 C 结构Noddy。那么我们将有:

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);
Run Code Online (Sandbox Code Playgroud)

这应该完成原始代码中的所有操作,包括调用PyType_Ready,以及创建动态类型所需的内容,包括设置Py_TPFLAGS_HEAPTYPE,以及为 a 分配和初始化额外的内存PyHeapTypeObject.

我希望这会有所帮助。


Dem*_*hun 1

尝试并了解如何执行此操作的一种方法是使用 SWIG 创建它的版本。看看它产生了什么,看看它是否匹配或以不同的方式完成。据我所知,编写 SWIG 的人对扩展 Python 有深入的了解。无论如何,看看他们如何做事也没什么坏处。它可能会帮助您理解这个问题。