返回函数 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
,您必须继续将 stdin
或 f
作为参数传递给 fread
,而不是 [=110] =] 的 io
,您可以在 sys.stdin
或 f
.
上调用方法
但更糟糕的是,因为没有静态或动态的类型检查来保护您免受某些 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++,它肯定有一个较低的学习曲线。
我正在为 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
,您必须继续将 stdin
或 f
作为参数传递给 fread
,而不是 [=110] =] 的 io
,您可以在 sys.stdin
或 f
.
但更糟糕的是,因为没有静态或动态的类型检查来保护您免受某些 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 *
.
还有很多工作要做。如果您还没有阅读 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++,它肯定有一个较低的学习曲线。