假设我们有 为 Python 编写 C 扩展模块的教程。现在我们要创建一个派生类型,仅覆盖 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);
这有效,但我不确定这是否是正确的方法。我预计我必须设置 Py_TPFLAGS_HEAPTYPE
< /a> 标志也是如此,因为我在堆上动态分配类型对象,但这样做会导致解释器中出现段错误。
我还考虑过使用 PyObject_Call()
或类似的显式调用 type()
,但我放弃了这个想法。我需要将函数 BrownNoddy_new()
包装在 Python 函数对象中,并创建一个将 __new__
映射到该函数对象的字典,这看起来很愚蠢。
解决这个问题的最佳方法是什么?我的做法正确吗?是否有我错过的接口功能?
更新
python-dev 邮件列表上有两个关于相关主题的线程 (1) (2)。从这些线程和一些实验中,我推断出我不应该设置 Py_TPFLAGS_HEAPTYPE
,除非类型是通过调用 type()
分配的。这些线程中有不同的建议,是手动分配类型更好还是调用 type()
更好。如果我知道包装应该放入 tp_new 槽中的 C 函数的推荐方法是什么,我会对后者感到满意。对于常规方法,此步骤很简单 - 我只需使用 PyDescr_NewMethod( )
获取合适的包装对象。不过,我不知道如何为我的 __new__() 方法创建这样的包装器对象 - 也许我需要未记录的函数 PyCFunction_New()
来创建这样的包装器目的。
Assume we have the type Noddy
as defined in the tutorial on writing C extension modules for Python. Now we want to create a derived type, overwriting only the __new__()
method of Noddy
.
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.
I also thought about explicitly calling type()
using PyObject_Call()
or similar, but I discarded the idea. 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.
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). 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()
. There are different recommendations in these threads whether it is better to allocate the type manually or to call 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. For regular methods this step would be easy -- I could just use PyDescr_NewMethod()
to get a suitable wrapper object. 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.
发布评论
评论(3)
当我修改扩展以兼容Python 3时,我遇到了同样的问题,并在尝试解决它时找到了这个页面。
我最终通过阅读Python解释器的源代码解决了这个问题,PEP 0384 以及 C-API 的文档。
设置
Py_TPFLAGS_HEAPTYPE
标志告诉解释器将PyTypeObject
重新转换为PyHeapTypeObject
,其中包含还必须分配的其他成员。在某些时候,解释器会尝试引用这些额外的成员,如果您不分配它们,则会导致段错误。Python 3.2 引入了 C 结构体
PyType_Slot
和PyType_Spec
以及 C 函数PyType_FromSpec
来简化动态类型的创建。简而言之,您使用PyType_Slot
和PyType_Spec
指定PyTypeObject
的tp_*
成员,然后调用 < code>PyType_FromSpec 来完成分配和初始化内存的脏工作。从 PEP 0384 开始,我们有:(
上面不是 PEP 0384 的文字副本,它还包括
const char *doc
作为PyType_Spec
的成员。但是该成员没有出现在源代码中。)要在原始示例中使用这些,假设我们有一个 C 结构
BrownNoddy
,它扩展了基类的 C 结构诺迪
。然后我们会:这应该完成原始代码中的所有操作,包括调用
PyType_Ready
,以及创建动态类型所需的操作,包括设置Py_TPFLAGS_HEAPTYPE
以及分配和初始化PyHeapTypeObject
的额外内存。我希望这有帮助。
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.
I did eventually solve it by reading the source code for the Python interpreter, PEP 0384 and the documentation for the C-API.
Setting the
Py_TPFLAGS_HEAPTYPE
flag tells the interpreter to recast yourPyTypeObject
asPyHeapTypeObject
, which contains additional members that must also be allocated. 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
andPyType_Spec
and the C functionPyType_FromSpec
that simplify the creation of dynamic types. In a nutshell, you usePyType_Slot
andPyType_Spec
to specify thetp_*
members of thePyTypeObject
and then callPyType_FromSpec
to do the dirty work of allocating and initialising the memory.From PEP 0384, we have:
(The above isn't a literal copy from PEP 0384, which also includes
const char *doc
as a member ofPyType_Spec
. But that member doesn't appear in the source code.)To use these in the original example, assume we have a C structure,
BrownNoddy
, that extends the C structure for the base classNoddy
. Then we would have:This should do everything in the original code, including calling
PyType_Ready
, plus what is necessary for creating a dynamic type, including settingPy_TPFLAGS_HEAPTYPE
, and allocating and initialising the extra memory for aPyHeapTypeObject
.I hope that's helpful.
如果这个答案很糟糕,我预先表示歉意,但是您可以在 PythonQt 中找到这个想法的实现,特别是我认为以下文件可能是有用的参考:
这个片段来自 PythonQtClassWrapper_init 的内容让我觉得有些有趣:
值得注意的是,PythonQt 确实使用了包装器生成器,所以它是不完全符合您的要求,但我个人认为试图超越 vtable 并不是最优化的设计。基本上,Python 有许多不同的 C++ 包装器生成器,人们使用它们是有充分理由的 - 它们被记录在案,搜索结果和堆栈溢出中也有一些示例。如果您手动推出一个以前没有人见过的解决方案,那么如果他们遇到问题,调试起来就会困难得多。即使它是闭源的,下一个维护它的人也会摸不着头脑,你必须向每个新来的人解释它。
一旦代码生成器开始工作,您所需要做的就是维护底层 C++ 代码,而不必手动更新或修改扩展代码。 (这可能与您使用的诱人解决方案相距不远)
建议的解决方案是打破新引入的类型安全的示例 PyCapsule 提供更多保护 反对(按指示使用时)。
因此,虽然它可能不是以这种方式实现派生/子类的最佳长期选择,而是包装代码并让 vtable 做它最擅长的事情,当新人有疑问时,你可以向他指出任何内容 解决方案 适合 最佳。
但这只是我的意见。 :D
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:
This fragment from PythonQtClassWrapper_init jumps out at me as being somewhat interesting:
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. 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. 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. (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).
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.
This is just my opinion though. :D
尝试并了解如何执行此操作的一种方法是使用 SWIG 创建它的版本。看看它产生了什么,看看它是否匹配或以不同的方式完成。据我所知,编写 SWIG 的人对扩展 Python 有深入的了解。无论如何,看看他们如何做事也没什么坏处。它可能会帮助您理解这个问题。
One way to try and understand how to do this is to create a version of it using 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. Can't hurt to see how they do things at any rate. It may help you understand this problem.