13

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

4

2 回答 2

21

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

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

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

在您的情况下,您想numpy.array在 Python 中使用 s 。因此,一般情况变为:

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

对于 1,这里是如何用 a 来做的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等效的(假设您可以依赖 Carray不被删除 - 请参阅在文档中创建数组以获取有关您的选项的更多详细信息):

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

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

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

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

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. 正如文档所说, to 的第二个参数[PyObject_CallObject][6]是一个元组,PyObject_CallObject(callable_object, args)相当于apply(callable_object, args),相当于callable_object(*args). 所以,如果你想myfunc(mylist)在 Python 中调用,你必须有效地将它转换成 C,myfunc(*(mylist,))这样你就可以将它翻译成 C。你可以tuple像这样构造一个:

PyObject *arglist = PyTuple_Pack(1, mylist);

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

那么,你是怎么得到的myfunc呢?好吧,如果您已经从嵌入代码创建了函数,只需保留指针即可。如果您希望它从 Python 代码中传递,这正是第一个示例所做的。例如,如果您想从模块或其他上下文中按名称查找它,具体类型PyModule和抽象类型的 APIPyMapping非常简单,如何将 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>可能就是您所需要的。

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

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

于 2012-12-18T22:04:23.540 回答
1

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

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

于 2012-12-18T22:12:29.790 回答