如何在Python CAPI中动态创建派生类型

2024-05-28 21:01:24 发布

您现在位置:Python中文网/ 问答频道 /正文

假设我们有在tutorial on writing C extension modules for Python中定义的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);

这是可行的,但我不确定这是不是正确的方法。我本以为我也必须设置^{}标志,因为我在堆上动态分配类型对象,但这样做会导致解释器中出现segfault。在

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

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

更新

在python dev邮件列表(1)(2)上有两个主题。从这些线程和一些实验中,我推断我不应该设置Py_TPFLAGS_HEAPTYPE,除非通过调用type()来分配类型。这些线程中有不同的建议,无论是手动分配类型还是调用type()。如果我知道包装应该放在tp_new槽中的C函数的推荐方法是什么,我会很乐意使用后者。对于常规方法,这一步很容易——我可以使用^{}来获得一个合适的包装器对象。我不知道如何为我的__new__()方法创建这样一个包装器对象——也许我需要一个未记录的函数PyCFunction_New()来创建这样的包装器对象。在


Tags: 对象方法函数py类型newtype线程
3条回答

尝试并理解如何做到这一点的一个方法是使用SWIG创建它的一个版本。看看它产生了什么,看看它是否匹配或是以不同的方式完成。据我所知,编写SWIG的人对扩展Python有着深入的理解。无论如何,看看他们是怎么做事的。它可以帮助你理解这个问题。在

我在修改扩展以与python3兼容时遇到了同样的问题,并在试图解决它时找到了这个页面。在

我最终通过阅读Python解释器的源代码PEP 0384和{a2}的文档解决了这个问题。在

设置Py_TPFLAGS_HEAPTYPE标志会告诉解释器将您的PyTypeObject重新转换为PyHeapTypeObject,其中包含必须分配的其他成员。在某些时候,解释器试图引用这些额外的成员,如果您不分配它们,它将导致segfault。在

python3.2引入了C结构PyType_Slot和{}以及简化动态类型创建的C函数PyType_FromSpec。简而言之,使用PyType_SlotPyType_Spec来指定PyTypeObjecttp_*成员,然后调用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*);

(以上不是pep0384的文本副本,它还包括const char *doc作为{}的成员。但源代码中没有该成员。)

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

^{pr2}$

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

我希望这对你有帮助。在

如果这个答案很糟糕,我很抱歉,但是您可以在PythonQt中找到这个想法的实现,特别是我认为以下文件可能是有用的参考:

来自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并不是最理想的设计。基本上,Python有许多不同的C++包装生成器,人们用它们来做一个很好的理由——它们被记录下来,在搜索结果和堆栈溢出中到处都有例子。如果你手动推出一个以前没人见过的解决方案,那么如果他们遇到问题,他们调试起来就会困难得多。即使是封闭源代码,下一个要维护它的人也会抓狂,你必须向每一个新来的人解释。在

< >一旦你得到一个代码生成器,你所需要做的就是维护底层C++代码,你不必手工更新或修改扩展代码。(这可能离你提出的诱人解决方案不太远)

建议的解决方案是一个破坏新引入的PyCapsule提供a bit more protection的类型安全性的示例(当按照指示使用时)。在

因此,虽然这样实现派生/子类可能不是最好的长期选择,而是包装代码,让vtable做它最擅长的事情,当新用户有问题时,您可以让他指向whateversolutionfitsbest的文档。在

不过,这只是我的看法。:天

相关问题 更多 >

    热门问题