我正在为C库创建Python绑定。
在C中,使用函数的代码如下所示:
Ihandle *foo;
foo = MethFunc();
SetArribute(foo, 's');
我正试着把这个放到Python中。其中有
MethFunc()
和SetAttribute()
函数可用于我的Python代码:import mymodule
foo = mymodule.MethFunc()
mymodule.SetAttribute(foo)
到目前为止,返回函数的C代码如下所示:
static PyObject * _MethFunc(PyObject *self, PyObject *args) {
return Py_BuildValue("O", MethFunc());
}
但失败在于崩溃(没有错误)
我也试过
return MethFunc();
,但失败了。我如何返回函数
foo
(或者如果我试图实现的是完全错误的,我应该如何将MethFunc()
传递到SetAttribute()
)?
最佳答案
这里的问题是MethFunc()
返回一个IHandle *
,但您告诉Python将其视为一个PyObject *
。大概那些是完全不相关的类型。PyObject *
(或者您或Python定义的以适当的struct
宏开头的任何HEAD
)以指向refcount和类型的指针开始,Python要对您手上的任何对象做的第一件事就是处理这些指针。因此,如果给它一个以两个int
s开头的对象,Python将最终尝试访问0x00020001
或类似的类型,这几乎肯定是segfault。
如果需要传递指向某个C对象的指针,则必须将其包装为Python对象。有三种方法可以做到这一点,从最简单到最可靠。
首先,可以将IHandle *
转换为size_t
,然后再将其转换为PyLong_FromSize_t
。
实现起来非常简单。但这意味着这些对象将看起来与Python端的数字一模一样,因为它们就是这样。
显然,您不能将方法附加到此数字;相反,您的API必须是一个自由函数,它接受一个数字,然后将该数字强制转换回IHandle*
并调用一个方法。
更像是,例如,C的stdio
,你必须不断地把stdin
或f
作为参数传递给fread
,而不是Python的io
,在sys.stdin
或f
上调用方法。
但更糟糕的是,因为没有静态或动态的类型检查来保护您免受某些Python代码意外地传递给您数字42
。然后将其转换为IHandle *
并尝试取消引用,从而导致segfault…
如果您希望Python的垃圾收集器能够帮助您知道对象何时仍然被引用,那么您就走运了。您需要让您的用户手动跟踪号码,并在完成后调用一些CloseHandle
函数。
实际上,这并不比从ctypes
访问代码好多少,所以希望这能激励您继续阅读。
更好的解决方案是将IHandle *
转换为void *
,然后再将其转换为PyCapsule_New
。
如果你还没有读过关于capsules的文章,你至少需要浏览一下主要章节。但基本思想是它将void*
包装成一个Python对象。
所以,它几乎和传递数字一样简单,但解决了大多数问题。胶囊是不透明的值,Python用户不能意外地对其进行运算;它们不能将42
发送给您来代替胶囊;您可以附加一个函数,当对胶囊的最后一个引用消失时调用该函数;您甚至可以给它一个很好的名称以显示在repr
中。
但你还是不能把任何行为附加到胶囊上。
因此,如果MethSetAttribute(mymodule, foo)
是胶囊,那么API仍然必须是mymeth.SetAttribute(foo)
而不是mymodule
,就像它是int
一样。(但现在它是类型安全的。)
最后,可以为包含IHandle *
的结构构建新的Python扩展类型。
这是更多的工作。如果你还没有读过关于Defining Extension Types的教程,你需要通读整个章节。
但这意味着您拥有一个实际的Python类型,以及与之相关的所有类型。
您可以给它一个SetAttribute
方法,而Python代码可以直接调用该方法。你可以给它任何你想要的东西。你可以给它一个__str__
。Python代码可以做到__repr__
。等等。
如果你愿意使用C++,或者D,或者锈,而不是C,有一些很棒的库(pycxx,Booo::python,pyd,锈蚀python等)可以为你做大部分的样板。您只需声明您想要一个Python类,并希望如何将其属性和方法绑定到C属性和方法中,并且可以得到像C++类一样使用的东西,除非它实际上是封面下的__doc__
。(它甚至可以通过RAII为您处理所有的refcounting积木,这将为您节省无尽的周末调试segfults和内存泄漏…)
或者您可以使用Cython,它允许您用一种基本上是Python的语言编写C扩展模块,但扩展到与C代码接口。因此,包装器类只是一个isinstance(mymodule, MyMeth)
,但是有一个特殊的privatePyObject *
属性来保存class
,而您的cdef
可以用这个private属性调用CIHandle *
函数。
或者,根据用户的建议,还可以使用SWIG为您生成C绑定。对于简单的情况,只需将C API提供给它就相当简单了,而且它还给了您构建PythonSetAttribute(self, s)
的代码。对于一些不太简单的案例,我个人觉得它比PyCxx之类的东西要痛苦得多,但是如果你还不知道C++的话,它肯定有一个更低的学习曲线。
关于python - 返回函数python-c-api,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51273804/