简体   繁体   English

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

[英]How to dynamically create a derived type in the Python C-API

Assume we have the type Noddy as defined in the tutorial on writing C extension modules for Python . 假定我们具有在为Python编写C扩展模块教程中定义的Noddy类型。 Now we want to create a derived type, overwriting only the __new__() method of Noddy . 现在我们要创建一个派生类型,仅覆盖Noddy__new__()方法。

Currently I use the following approach (error checking stripped for readability): 当前,我使用以下方法(去除了可读性的错误检查):

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);

This works, but I'm not sure if it is The Right Way To Do It. 这可行,但是我不确定这是否是正确的方法。 I would have expected that I have to set the Py_TPFLAGS_HEAPTYPE flag, too, because I dynamically allocate the type object on the heap, but doing so leads to a segfault in the interpreter. 我本来希望我也必须设置Py_TPFLAGS_HEAPTYPE标志,因为我会在堆上动态分配类型对象,但这样做会导致解释器出现段错误。

I also thought about explicitly calling type() using PyObject_Call() or similar, but I discarded the idea. 我还考虑过使用PyObject_Call()或类似方法显式调用type() ,但是我放弃了这个想法。 I would need to wrap the function BrownNoddy_new() in a Python function object and create a dictionary mapping __new__ to this function object, which seems silly. 我需要将函数BrownNoddy_new()包装在Python函数对象中,并创建一个将__new__映射到此函数对象的字典,这似乎很愚蠢。

What is the best way to go about this? 最好的方法是什么? Is my approach correct? 我的方法正确吗? Is there an interface function I missed? 我错过了接口功能吗?

Update 更新资料

There are two threads on a related topic on the python-dev mailing list (1) (2) . 在python-dev邮件列表(1) (2)上,有两个主题相关的主题。 From these threads and a few experiments I deduce that I shouldn't set Py_TPFLAGS_HEAPTYPE unless the type is allocated by a call to type() . 从这些线程和一些实验中,我Py_TPFLAGS_HEAPTYPE除非通过调用type()分配类型,否则不应该设置Py_TPFLAGS_HEAPTYPE There are different recommendations in these threads whether it is better to allocate the type manually or to call type() . 在这些线程中有不同的建议,是手动分配类型还是调用type()更好。 I'd be happy with the latter if only I knew what the recommended way to wrap the C function that is supposed to go in the tp_new slot is. 如果只有我知道包装应该放在tp_new插槽中的C函数的推荐方式是什么,我会对后者感到满意。 For regular methods this step would be easy -- I could just use PyDescr_NewMethod() to get a suitable wrapper object. 对于常规方法,此步骤很容易-我可以使用PyDescr_NewMethod()获得合适的包装对象。 I don't know how to create such a wrapper object for my __new__() method, though -- maybe I need the undocumented function PyCFunction_New() to create such a wrapper object. 但是,我不知道如何为__new__()方法创建这样的包装对象—也许我需要未记录的函数PyCFunction_New()来创建这样的包装对象。

I encountered the same problem when I was modifying an extension to be compatible with Python 3, and found this page when I was trying to solve it. 我在修改扩展使其与Python 3兼容时遇到了相同的问题,并在尝试解决该问题时找到了此页面。

I did eventually solve it by reading the source code for the Python interpreter, PEP 0384 and the documentation for the C-API . 我确实通过阅读Python解释器的源代码PEP 0384C-API的文档来解决了这个问题。

Setting the Py_TPFLAGS_HEAPTYPE flag tells the interpreter to recast your PyTypeObject as PyHeapTypeObject , which contains additional members that must also be allocated. 设置Py_TPFLAGS_HEAPTYPE标志告诉解释重铸你PyTypeObjectPyHeapTypeObject ,其中包含必须分配额外的成员。 At some point the interpreter attempts to refer to these extra members and, if you leave them unallocated, it will cause a segfault. 在某些时候,解释器会尝试引用这些额外的成员,并且,如果您不分配它们,将导致段错误。

Python 3.2 introduced the C structures PyType_Slot and PyType_Spec and the C function PyType_FromSpec that simplify the creation of dynamic types. Python 3.2引入了C结构PyType_SlotPyType_Spec以及C函数PyType_FromSpec ,它们简化了动态类型的创建。 In a nutshell, you use PyType_Slot and PyType_Spec to specify the tp_* members of the PyTypeObject and then call PyType_FromSpec to do the dirty work of allocating and initialising the memory. 简而言之,您使用PyType_SlotPyType_Spec指定tp_*的成员PyTypeObject然后调用PyType_FromSpec做分配和初始化内存的肮脏的工作。

From PEP 0384, we have: 从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*);

(The above isn't a literal copy from PEP 0384, which also includes const char *doc as a member of PyType_Spec . But that member doesn't appear in the source code.) (以上不是PEP 0384的文字副本,它还包含const char *doc作为PyType_Spec的成员。但是该成员未出现在源代码中。)

To use these in the original example, assume we have a C structure, BrownNoddy , that extends the C structure for the base class Noddy . 要在原始示例中使用它们,假设我们有一个C结构BrownNoddy ,它为基类Noddy扩展了C结构。 Then we would have: 然后我们将有:

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);

This should do everything in the original code, including calling PyType_Ready , plus what is necessary for creating a dynamic type, including setting Py_TPFLAGS_HEAPTYPE , and allocating and initialising the extra memory for a PyHeapTypeObject . 这应该执行原始代码中的所有操作,包括调用PyType_Ready以及创建动态类型所需的操作,包括设置Py_TPFLAGS_HEAPTYPE以及为PyHeapTypeObject分配和初始化额外的内存。

I hope that's helpful. 希望对您有所帮助。

I apologize up front if this answer is terrible, but you can find an implementation of this idea in PythonQt , in particular I think the following files might be useful references: 如果这个答案很糟糕,我先向您道歉,但是您可以在PythonQt中找到此想法的实现 ,尤其是我认为以下文件可能是有用的参考:

This fragment from PythonQtClassWrapper_init jumps out at me as being somewhat interesting: 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;
}

It's worth noting that PythonQt does use a wrapper generator, so it's not exactly in line with what you're asking for, but personally I think trying to outsmart the vtable isn't the most optimal design. 值得注意的是,PythonQt确实使用了包装器生成器,因此它与您的要求并不完全一致,但是我个人认为试图超越vtable并不是最佳的设计。 Basically, there are many different C++ wrapper generators for Python and people use them for a good reason - they're documented, there are examples floating around in search results and on stack overflow. 基本上,有许多不同的Python C ++包装器生成器,人们出于充分的理由使用它们-已被记录在案,在搜索结果中和堆栈溢出时都有示例出现。 If you hand roll a solution for this that nobody's seen before, it'll be that much harder for them to debug if they run into problems. 如果您为此解决方案提供了前所未有的解决方案,那么如果遇到问题,调试起来将变得更加困难。 Even if it's closed-source, the next guy who has to maintain it will be scratching his head and you'll have to explain it to every new person who comes along. 即使是开源的,下一个必须维护的人也会挠头,您必须向出现的每个新人解释。

Once you get a code generator working, all you need to do is maintain the underlying C++ code, you don't have to update or modify your extension code by hand. 一旦代码生成器正常工作,您所需要做的就是维护基础C ++代码,而无需手动更新或修改扩展代码。 (Which is probably not too far away from the tempting solution you went with) (与您使用的诱人解决方案可能相距不远)

The proposed solution is an example of breaking the type-safety that the newly introduced PyCapsule provides a bit more protection against (when used as directed). 提出的解决方案是打破类型安全性的一个示例,这种安全性是新引入的PyCapsule提供了更多的保护 (按指示使用时)。

So, while its possible it might not be the best long term choice to implement derived/subclasses this way, but rather wrap the code and let the vtable do what it does best and when the new guy has questions you can just point him at the documentation for whatever solution fits best . 因此,虽然可能不是用这种方式实现派生/子类的最佳长期选择,而是包装代码并让vtable做到最好,而当新手有疑问时,您可以将他指向对于文档的任何 解决方案 适合 最好

This is just my opinion though. 不过这只是我的意见。 :D :D

One way to try and understand how to do this is to create a version of it using SWIG. 尝试并了解如何执行此操作的一种方法是使用SWIG创建其版本。 See what it produces and see if it matches or is done a different way. 查看它产生了什么,并查看它是否匹配或以其他方式完成。 From what I can tell the people who have been writing SWIG have an in depth understanding of extending Python. 据我所知,一直在编写SWIG的人们对扩展Python有深入的了解。 Can't hurt to see how they do things at any rate. 看到他们如何做事情就不会受伤。 It may help you understand this problem. 它可以帮助您了解此问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM