NumPy数组C API

8
我有一个返回std::vector的C++函数,我想在Python中使用它,所以我正在使用C numpy API:
static PyObject *
py_integrate(PyObject *self, PyObject *args){
    ...
    std::vector<double> integral;
    cpp_function(integral);  // This changes integral
    npy_intp size = {integral.size()};
    PyObject *out = PyArray_SimpleNewFromData(1, &size, NPY_DOUBLE, &(integral[0]));
    return out;
}

以下是我在Python中的调用方法:

import matplotlib.pyplot as plt

a = py_integrate(parameters)
print a
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(a)
print a

发生的情况是:第一次打印正常,值是正确的。但是当我绘制 a 时,它们不正确;在第二次打印中,我看到非常奇怪的值,例如 1E-308 1E-308 ...0 0 0 ...,这是未初始化的内存。我不明白为什么第一次打印正常。

部分解决方案(不起作用):

static void DeleteVector(void *ptr)
{
    std::cout << "Delete" << std::endl;
    vector * v = static_cast<std::vector<double> * >(ptr);
    delete v;
    return;
}

static PyObject *
cppfunction(PyObject *self, PyObject *args)
{
    std::vector<double> *vector = new std::vector<double>();
    vector->push_back(1.);
    PyObject *py_integral = PyCObject_FromVoidPtr(vector, DeleteVector);
    npy_intp size = {vector->size()};
    PyArrayObject *out;
    ((PyArrayObject*) out)->base = py_integral;
    return (PyObject*)(out);
}
2个回答

11
您的std::vector对象似乎是该函数局部变量。 PyArray_SimpleNewFromData不会复制您传递给它的数据。 它只保留一个指针。因此,一旦您的py_integrate函数返回,向量将被释放。第一次打印工作是因为尚未写入已释放的内存,但是到达下一个打印时,其他内容已使用了该内存,导致值不同。
您需要创建一个拥有自己存储空间的NumPy数组,然后将数据复制到其中。
或者,可以在堆上分配向量。 然后在CObject中存储指针。 提供一个析构函数以删除向量。 然后,查看C级别的PyArrayObject类型。 它有一个名为basePyObject *成员。 在那里存储您的CObject。 然后,当NumPy数组被垃圾回收时,基对象上的引用计数将减少,假设您没有在其他地方复制它,由于提供的析构函数,您的向量将被删除。

修复程序

您忘记实际创建PyArray。 试试这个:

(您没有发布DeleteVector,所以我只能希望它是正确的)

std::vector<double> *vector = new std::vector<double>();
vector->push_back(1.);
PyObject *py_integral = PyCObject_FromVoidPtr(vector, DeleteVector);
npy_intp size = {vector->size()};
PyObject *out = PyArray_SimpleNewFromData(1, &size, NPY_DOUBLE, &((*vector)[0]));
((PyArrayObject*) out)->base = py_integral;
return out;

注意:我不是C++程序员,所以我只能假设&((*vector)[0])可以与指向向量的指针一同使用。我确实知道如果你增加了它的大小,向量会重新分配其存储区域,因此在获取该指针后不要增加其大小,否则它将不再有效。


谢谢,"提供一个删除向量的析构函数"是什么意思?std::vector已经有它自己的析构函数了。 - Ruggero Turra
@wiso - 是的,std::vector 有一个析构函数,但是 Python CObject 没有,它负责删除向量。您只需要提供一个删除其中包含的向量的小函数。 - kwatford
Python是用C语言编写的。 CObject是Python封装指向任意对象的指针作为Python对象的方式。您需要Python跟踪您的向量,以便在numpy数组之后可以删除它。然而,Python不理解C ++,因此您必须告诉它如何删除向量。这在我提供的CObject文档链接中有说明。如果您无法弄清楚,请改用第一种选项。 - kwatford
抱歉,kwatford,你能修复我的代码吗(问题中的不起作用的解决方案)? - Ruggero Turra
嗯,根据你发布的内容,我无法确定问题出在哪里。但是看起来你自己打了那段代码(我可以从 doubble 拼写错误看出来),所以你可能不小心漏掉了某些东西。你可以复制并粘贴所有内容,包括修改后的内容,以获得更准确的重现。 - kwatford
显示剩余5条评论

0

由于向量将超出范围并且在您需要它时内存将不再可用(如kwatford所述),因此您需要复制向量。

一种制作所需Numpy数组(通过复制数据)的方法是:

PyObject *out = nullptr;

std::vector<double> *vector = new std::vector<double>();
vector->push_back(1.);

npy_intp size = {vector.size()};

out = PyArray_SimpleNew(1, &size, NPY_DOUBLE);

memcpy(PyArray_DATA((PyArrayObject *) out), vector.data(), vector.size());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接