返回函数 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)

到目前为止,我的 return 函数的 C 代码如下所示:

static PyObject * _MethFunc(PyObject *self, PyObject *args) {
    return Py_BuildValue("O", MethFunc());
}

但是由于崩溃而失败(没有错误)

我也试过 return MethFunc(); 但失败了。

我如何 return 函数 foo (或者如果我想要实现的是完全错误的,我应该如何将 MethFunc() 传递给 SetAttribute() )?

这里的问题是 MethFunc() returns 一个 IHandle *,但是你告诉 Python 把它当作一个 PyObject *。大概那些是完全不相关的类型。

A PyObject *(或任何 struct 您或 Python 定义以适当的 HEAD 宏开头的)以指向引用计数和类型的指针开头,并且Python 对您递交的任何对象要做的第一件事就是处理这些指针。所以,如果你给它一个对象,而不是以两个 int 开头,Python 将最终尝试访问 0x00020001 或类似的类型,这几乎是肯定会出现段错误。

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


首先,您可以将 IHandle * 转换为 size_t,然后 PyLong_FromSize_t

这实现起来非常简单。但这意味着这些对象将看起来与 Python 一侧的数字完全一样,因为它们就是这样。

显然你不能给这个号码附加方法;相反,您的 API 必须是一个自由函数,它接受一个数字,然后将该数字转换回 IHandle* 并调用一个方法。

它更像是 C 的 stdio,您必须继续将 stdinf 作为参数传递给 fread,而不是 [=110] =] 的 io,您可以在 sys.stdinf.

上调用方法

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

如果您希望 Python 的垃圾收集器能帮助您了解对象何时仍被引用,那您就倒霉了。您需要让您的用户手动跟踪号码并在他们完成后调用一些 CloseHandle 函数。

真的,这并不比从 ctypes 访问您的代码好多少,所以希望这能激励您继续阅读。


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

如果您还没有读过 capsules,您至少需要浏览一下主要章节。但基本思想是将 void* 包装为 Python 对象。

所以,它几乎就像传递数字一样简单,但解决了大部分问题。胶囊是不透明的值,您的 Python 用户不会不小心对其进行算术运算;他们无法向您发送 42 代替胶囊;您可以附加一个函数,当对胶囊的最后一次引用消失时调用该函数;你甚至可以给它起一个好听的名字,让它出现在 repr.

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

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


最后,您可以为包含 IHandle *.

的结构构建一个新的 Python 扩展类型

还有很多工作要做。如果您还没有阅读 Defining Extension Types 上的教程,则需要通读整章。

但这意味着您拥有一个实际的 Python 类型,以及与之相关的所有内容。

你可以给它一个SetAttribute方法,Python代码可以直接调用那个方法。你可以给它任何你想要的__str____repr__。你可以给它一个__doc__。 Python 代码可以做到 isinstance(mymodule, MyMeth)。等等。


如果您愿意使用 C++、D 或 Rust 而不是 C,可以使用一些很棒的库(PyCxx、boost::python、Pyd、rust-python 等)可以为您完成大部分样板文件。你只需声明你想要一个 Python class 以及你希望它的属性和方法如何绑定到你的 C 属性和方法,你就得到了一些你可以像 C++ class 一样使用的东西,除了它实际上是一个 PyObject * 在幕后。 (它甚至会通过 RAII 为您处理所有的引用计数问题,这将为您节省无休止的周末调试段错误和内存泄漏……)

或者您可以使用 Cython,它允许您使用基本上 Python 但扩展为与 C 代码接口的语言编写 C 扩展模块。所以你的包装器 class 只是一个 class,但是有一个特殊的私有 cdef 属性来保存 IHandle *,而你的 SetAttribute(self, s) 可以调用 C SetAttribute 具有该私有属性的函数。

或者,根据用户的建议,您也可以使用 SWIG 为您生成 C 绑定。对于简单的情况,它非常简单——只需将你的 C API 提供给它,它就会返回代码来构建你的 Python .so。对于不太简单的情况,我个人觉得它比 PyCxx 之类的东西要痛苦得多,但如果你还不知道 C++,它肯定有一个较低的学习曲线。