繁体   English   中英

返回函数python-c-api

[英]Returning a function python-c-api

我正在为C库创建Python绑定。

在C语言中,使用这些函数的代码如下所示:

Ihandle *foo;
foo = MethFunc();
SetArribute(foo, 's');

我正在尝试将其纳入Python。 我在Python代码中可以使用MethFunc()SetAttribute()函数的地方:

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定义的以适当的HEAD宏开头的任何struct )都以指向refcount和类型的指针开头,而Python将要处理的所有对象的第一件事就是处理这些指针。 因此,如果给它一个以两个int开头的对象,则Python最终将尝试访问0x00020001或类似的类型,这几乎肯定会导致segfault。

如果需要传递指向某个C对象的指针,则必须将其包装在Python对象中。 有三种方法可以做到这一点,从最棘手到最可靠。


首先,您可以将IHandle *转换为size_t ,然后PyLong_FromSize_tPyLong_FromSize_t它。

这实在太简单了。 但这意味着从Python的角度来看,这些对象看起来将完全像数字,因为仅此而已。

显然,您不能在此号码上附加方法。 相反,您的API必须是一个使用数字的自由函数,然后将该数字转换回IHandle*并调用一个方法。

例如,它更像是C的stdio ,在其中您必须不断传递stdinf作为fread的参数,而不是Python的io ,在sys.stdinf上调用方法。

但更糟糕的是,因为没有静态或动态类型检查来保护您免受某些Python代码的意外破坏,该代码意外地将数字42传递给您。 然后将其转换为IHandle *并尝试取消引用,从而导致段错误…

而且,如果您希望Python的垃圾收集器可以帮助您知道何时仍引用该对象,那您就不走运了。 您需要让您的用户手动跟踪该数字,并在使用CloseHandle调用一些CloseHandle函数。

确实,这并不比从ctypes访问代码好多少,因此希望能激发您继续阅读的兴趣。


更好的解决方案是将IHandle *转换为void * ,然后PyCapsule_New

如果您还没有阅读关于Capsule的文章 ,则至少需要略读本章。 但基本思想是,它将void*包装为Python对象。

因此,这几乎就像传递数字一样简单,但是解决了大多数问题。 胶囊是不透明的值,您的Python用户不能意外地对其进行算术; 他们无法寄给您42代替胶囊; 您可以附加一个函数,该函数在对胶囊的最后一个引用消失时被调用; 您甚至可以给它起一个漂亮的名字,以显示在repr

但是您仍然无法将任何行为附加到胶囊上。

因此,如果mymodule是一个封装,就像它是一个int一样,您的API仍然必须是MethSetAttribute(mymodule, foo)而不是mymeth.SetAttribute(foo) (除了现在是类型安全的。)


最后,您可以为包含IHandle *的结构构建新的Python扩展类型。

这还有很多工作要做。 而且,如果您还没有阅读有关定义扩展类型的教程,则需要仔细阅读整个章节。

但这意味着您拥有一个实际的Python类型,并附带所有相关内容。

您可以给它一个SetAttribute方法,Python代码可以调用该方法。 您可以根据需要提供__str____repr__ 您可以给它一个__doc__ Python代码可以执行isinstance(mymodule, MyMeth) 等等。


如果您愿意使用C ++或D或Rust而不是C,那么有一些很棒的库(PyCxx,boost :: python,Pyd,rust-python等)可以为您完成大部分样板工作。 您只需要声明一个Python类以及将其属性和方法绑定到C属性和方法的方式,就可以得到像C ++类一样可以使用的东西,只是它实际上是一个PyObject * (它甚至可以通过RAII为您解决所有的刷新问题,这将节省您无尽的周末调试段错误和内存泄漏的时间...)

或者,您可以使用Cython ,它使您可以使用基本上是Python的语言编写C扩展模块,但可以扩展为与C代码接口。 因此,您的包装器类只是一个class ,但是具有一个特殊的私有cdef属性,该属性包含IHandle * ,并且您的SetAttribute(self, s)可以仅使用该私有属性调用C SetAttribute函数。

或者,根据用户的建议,您也可以使用SWIG为您生成C绑定。 在简单的情况下,这很简单-只需将其提供给您的C API,它就会为您提供构建Python .so的代码。 对于不太简单的情况,我个人觉得它比PyCxx之类的要痛苦得多,但是如果您不了解C ++,它的学习曲线肯定会更低。

暂无
暂无

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

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