numpy数组C API

8 投票
2 回答
6645 浏览
提问于 2025-04-15 23:15

我有一个C++的函数,它返回一个std::vector,我想在Python中使用它,所以我用的是C的numpy接口:

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 个回答

0

你需要先复制一份这个向量,因为当你在Python中需要它的时候,原来的向量会失效,内存也就不能用了(正如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());
11

你的 std::vector 对象似乎只在那个函数内部使用。PyArray_SimpleNewFromData 并不会复制你传入的数据,它只是保留了一个指向数据的指针。所以当你的 py_integrate 函数返回后,这个向量就被释放了。第一次打印的时候能正常工作是因为还没有其他东西覆盖掉那块被释放的内存,但等到你进行下一次打印时,其他东西已经使用了那块内存,导致值变了。

你需要创建一个拥有自己存储空间的 NumPy 数组,然后把数据复制到里面。

另外,你可以把向量分配在堆上。然后在一个 CObject 中存储它的指针。提供一个析构函数来删除这个向量。接着,看看 C 级的 PyArrayObject 类型。它有一个叫 basePyObject * 成员。把你的 CObject 存储在这里。这样,当 NumPy 数组被垃圾回收时,这个 base 对象的引用计数会减少,假设你没有在其他地方复制它,你的向量就会通过你提供的析构函数被删除。

修复建议

你忘了实际创建 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]) 在指向向量时能按预期工作。我知道如果你扩展向量,它会重新分配存储空间,所以在获取那个指针后不要增加它的大小,否则指针就不再有效了。

撰写回答