如何将数组从 C 传递到嵌入式 python 脚本

Posted

技术标签:

【中文标题】如何将数组从 C 传递到嵌入式 python 脚本【英文标题】:How to pass an array from C to an embedded python script 【发布时间】:2012-12-06 04:47:41 【问题描述】:

我遇到了一些问题,希望得到帮助。我有一段代码,用于嵌入 python 脚本。这个 python 脚本包含一个函数,它期望接收一个数组作为参数(在这种情况下,我在 python 脚本中使用 numpy 数组)。 我想知道如何将一个数组从 C 传递到嵌入式 python 脚本作为脚本中函数的参数。更具体地说,谁能给我看一个简单的例子。

【问题讨论】:

【参考方案1】:

真的,这里最好的答案可能是只使用numpy 数组,即使是从您的C 代码中也是如此。但如果这不可能,那么您将遇到与在 C 类型和 Python 类型之间共享数据的任何代码相同的问题。

一般来说,在 C 和 Python 之间共享数据至少有五个选项:

    创建一个 Python list 或其他要传递的对象。 定义一个新的 Python 类型(在您的 C 代码中)以包装和表示数组,使用您在 Python 中为序列对象定义的相同方法(__getitem__ 等)。 将指向数组的指针转换为intptr_t,或显式ctypes 类型,或者不进行转换;然后在 Python 端使用ctypes 来访问它。 将指向数组的指针转换为const char * 并将其作为str 传递(或者,在Py3 中,bytes),并在Python 端使用structctypes 来访问它。 创建一个与buffer 协议匹配的对象,然后在Python 端再次使用structctypes

在您的情况下,您想在 Python 中使用 numpy.arrays。所以,一般情况变成:

    创建numpy.array 以通过。 (可能不合适) 按原样传递指向数组的指针,并在 Python 中使用 ctypes 将其转换为 numpy 可以转换为数组的类型。 将指向数组的指针转换为const char *,并将其作为str(或在Py3 中为bytes)传递,这已经是numpy 可以转换为数组的类型。 创建一个匹配buffer 协议的对象,我相信numpy 可以直接转换。

对于 1,这里是使用 list 的方法,因为这是一个非常简单的示例(而且我已经写过......):

PyObject *makelist(int array[], size_t size) 
    PyObject *l = PyList_New(size);
    for (size_t i = 0; i != size; ++i) 
        PyList_SET_ITEM(l, i, PyInt_FromLong(array[i]));
    
    return l;

这是 numpy.array 等效项(假设您可以依赖 C array 不会被删除 - 请参阅文档中的 Creating arrays 以了解有关您的选项的更多详细信息):

PyObject *makearray(int array[], size_t size) 
    npy_int dim = size;
    return PyArray_SimpleNewFromData(1, &dim, (void *)array);

无论如何,不​​管你怎么做,你最终会得到一个看起来像 C 中的 PyObject * 的东西(并且有一个引用计数),所以你可以将它作为函数参数传递,而在 Python 端它看起来像 numpy.arraylistbytes 或其他任何合适的。

现在,您如何实际传递函数参数?好吧,您在评论中引用的Pure Embedding 中的示例代码显示了如何执行此操作,但并没有真正解释发生了什么。实际上,扩展文档中的解释比嵌入文档要多,具体来说,Calling Python Functions from C。另外,请记住,standard library source code 充满了这样的例子(尽管其中一些不是因为优化,或者只是因为它们没有被更新以利用新的简化 C API 功能)。

跳过关于从 Python 获取 Python 函数的第一个示例,因为大概您已经拥有了。第二个示例(以及与之相关的段落)显示了一种简单的方法:使用Py_BuildValue 创建一个参数元组。因此,假设我们要调用存储在myfunc 中的函数,并使用上面的makelist 函数返回的列表mylist。这是你要做的:

if (!PyCallable_Check(myfunc)) 
    PyErr_SetString(PyExc_TypeError, "function is not callable?!");
    return NULL;

PyObject *arglist = Py_BuildValue("(o)", mylist);
PyObject *result = PyObject_CallObject(myfunc, arglist);
Py_DECREF(arglist);
return result;

当然,如果你确定你有一个有效的可调用对象,你可以跳过可调用检查。 (如果合适的话,通常最好检查一下你第一次收到myfunc 的时间,因为这样你可以提供更早更好的错误反馈。)

如果您想真正了解发生了什么,请尝试不使用Py_BuildValue。正如文档所说,[PyObject_CallObject][6] 的第二个参数是一个元组,PyObject_CallObject(callable_object, args) 相当于 apply(callable_object, args),相当于 callable_object(*args)。因此,如果您想在 Python 中调用 myfunc(mylist),则必须将其有效地转换为 myfunc(*(mylist,)),以便将其转换为 C。您可以像这样构造 tuple

PyObject *arglist = PyTuple_Pack(1, mylist);

但通常,Py_BuildValue 更容易(特别是如果您还没有将所有内容打包为 Python 对象),并且代码中的意图更清晰(就像使用 PyArg_ParseTuple 比使用显式 @ 更简单和清晰一样987654383@ 在另一个方向起作用)。

那么,您是如何获得 myfunc 的?好吧,如果您已经从嵌入代码创建了函数,只需保留指针即可。如果您希望它从 Python 代码中传递,这正是第一个示例所做的。例如,如果您想从模块或其他上下文中按名称查找它,PyModule 等具体类型和PyMapping 等抽象类型的 API 非常简单,如何将 Python 代码转换为等效的 C 代码,即使结果大多是丑陋的样板。

总而言之,假设我有一个 C 整数数组,我想要 import mymodule 并调用一个函数 mymodule.myfunc(mylist) 返回一个 int。这是一个精简的示例(未经实际测试,也没有错误处理,但它应该显示所有部分):

int callModuleFunc(int array[], size_t size) 
    PyObject *mymodule = PyImport_ImportModule("mymodule");
    PyObject *myfunc = PyObject_GetAttrString(mymodule, "myfunc");
    PyObject *mylist = PyList_New(size);
    for (size_t i = 0; i != size; ++i) 
        PyList_SET_ITEM(l, i, PyInt_FromLong(array[i]));
    
    PyObject *arglist = Py_BuildValue("(o)", mylist);
    PyObject *result = PyObject_CallObject(myfunc, arglist);
    int retval = (int)PyInt_AsLong(result);
    Py_DECREF(result);
    Py_DECREF(arglist);
    Py_DECREF(mylist);
    Py_DECREF(myfunc);
    Py_DECREF(mymodule);
    return retval;

如果您使用 C++,您可能想研究某种范围保护/看门人/等。处理所有那些Py_DECREF 调用,尤其是当您开始进行正确的错误处理时(这通常意味着早期的return NULL 调用贯穿该函数)。如果您使用 C++11 或 Boost,unique_ptr<PyObject, Py_DecRef> 可能就是您所需要的。

但实际上,如果您打算进行大量 CPython 通信,那么减少所有丑陋样板的更好方法是查看所有为改进 Python 扩展而设计的熟悉框架—Cython, boost::python 等。即使您正在嵌入,您实际上也在做与扩展相同的工作,因此它们可以以相同的方式提供帮助。

就此而言,如果您搜索文档,其中一些具有帮助嵌入部分的工具。例如,您可以使用 Cython 编写主程序,同时使用 C 代码和 Python 代码,以及 cython --embed。您可能想要交叉手指和/或牺牲一些鸡,但如果它有效,它会非常简单和富有成效。 Boost 的开始并不是那么简单,但是一旦你把所有的东西放在一起,几乎所有事情都会按照你期望的方式完成,并且可以正常工作,这对于 embedding 和扩展一样正确。以此类推。

【讨论】:

“如果你能确定 C 数组的寿命会和……一样长”——你说错了。 PyArray_SimpleNewFromData()PyArray_NewFromDescr() 都不会复制数据。前者总是期望传入一个缓冲区,而后者也接受一个空指针,在这种情况下它将分配一个新的缓冲区(当数组对象被垃圾回收时会自动释放)。无论如何,将数据复制到新缓冲区仍然是您的工作。 感谢您的深入解释。所以现在我正在尝试你的列表示例。我的下一个问题是如何将此列表作为参数传递给函数。我正在关注这个例子(docs.python.org/2/extending/embedding.html,关于纯嵌入的例子),当他们想要调用 python 函数时,他们使用 PyObject_CallObject 并查看文档,它说它需要一个元组作为参数。你认为你能帮我解决这个问题吗? @user1750948:我将更新答案,详细说明如何传递一般参数,以单个 list 参数作为具体示例。 @SvenMarnach:numpy 网站首页已经坏了整整一周,而且我没有文档的本地副本……但显然对网站内页面的内部引用工作正常,这我没有意识到(所以我在记忆中工作,并简要浏览了周围的旧代码)。我删除了误导性和不正确的信息,并添加了文档链接。感谢您接听! 我终于让你的代码工作了!有一个错误!这不是小O,而是大O!在 Py_BuildValue("(o)", mylist);见docs.python.org/2.2/ext/buildValue.html【参考方案2】:

Python 函数需要传入一个 Python 对象。由于您希望该 Python 对象是一个 NumPy 数组,因此您应该使用 NumPy C-API functions for creating arrays 之一; PyArray_SimpleNewFromData() 可能是一个好的开始。它将使用提供的缓冲区,而不复制数据。

也就是说,用 Python 编写主程序并为 C 代码使用 C 扩展模块几乎总是更容易。这种方法更容易让 Python 进行内存管理,ctypes 模块与 Numpy 的cpython 扩展一起使将 NumPy 数组传递给 C 函数变得容易。

【讨论】:

以上是关于如何将数组从 C 传递到嵌入式 python 脚本的主要内容,如果未能解决你的问题,请参考以下文章

如何从python脚本将数组传递给php?

如何使用 Python C/C++ 接口将实例成员函数作为 PyCFunction 类型传递

如何将多个图像作为输入传递给python脚本

将参数从cmd传递到python脚本[重复]

如何正确地将浮点指针从 C 库传递到其 C# 包装器

将字典数组传递给python