python - 返回函数python-c-api

标签 python c python-c-api

我正在为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要对您手上的任何对象做的第一件事就是处理这些指针。因此,如果给它一个以两个ints开头的对象,Python将最终尝试访问0x00020001或类似的类型,这几乎肯定是segfault。
如果需要传递指向某个C对象的指针,则必须将其包装为Python对象。有三种方法可以做到这一点,从最简单到最可靠。
首先,可以将IHandle *转换为size_t,然后再将其转换为PyLong_FromSize_t
实现起来非常简单。但这意味着这些对象将看起来与Python端的数字一模一样,因为它们就是这样。
显然,您不能将方法附加到此数字;相反,您的API必须是一个自由函数,它接受一个数字,然后将该数字强制转换回IHandle*并调用一个方法。
更像是,例如,C的stdio,你必须不断地把stdinf作为参数传递给fread,而不是Python的io,在sys.stdinf上调用方法。
但更糟糕的是,因为没有静态或动态的类型检查来保护您免受某些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/

相关文章:

Python程序使用类程序模拟掷两个骰子

python - 在Django中的html模式中加载动态数据

c - 线程程序问题

C - 不能将 strcpy 与 strtok 一起使用

python - 如果 PyModule_Add* 函数失败,C 扩展是否应该在模块初始化中失败?

opencv - 如何在C++ OpenCV代码中将PyObject变量转换为Mat

Python C API - 如何为 eval 表达式赋值?

python sklearn cross_validation/标签数量与样本数量不匹配

python - 在 linux 上用 python 从 postfix 读取邮件

c++ - 如何从应用程序中更改 "Dim the display"和 "Dimming display brightness"设置 (Windows)?