[英]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_t
其PyLong_FromSize_t
它。
这实在太简单了。 但这意味着从Python的角度来看,这些对象看起来将完全像数字,因为仅此而已。
显然,您不能在此号码上附加方法。 相反,您的API必须是一个使用数字的自由函数,然后将该数字转换回IHandle*
并调用一个方法。
例如,它更像是C的stdio
,在其中您必须不断传递stdin
或f
作为fread
的参数,而不是Python的io
,在sys.stdin
或f
上调用方法。
但更糟糕的是,因为没有静态或动态类型检查来保护您免受某些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.