使用capi访问NumPy数组的视图

2024-09-30 18:33:36 发布

您现在位置:Python中文网/ 问答频道 /正文

在C++中编写的Python扩展模块中,我使用以下代码片段将NUMPY数组转换成Armadillo数组,用于代码的C++部分:

static arma::mat convertPyArrayToArma(PyArrayObject* pyarr, int nrows, int ncols)
{
    // Check if the dimensions are what I expect.
    if (!checkPyArrayDimensions(pyarr, nrows, ncols)) throw WrongDimensions();

    const std::vector<int> dims = getPyArrayDimensions(pyarr);  // Gets the dimensions using the API

    PyArray_Descr* reqDescr = PyArray_DescrFromType(NPY_DOUBLE);
    if (reqDescr == NULL) throw std::bad_alloc();

    // Convert the array to Fortran-ordering as required by Armadillo
    PyArrayObject* cleanArr = (PyArrayObject*)PyArray_FromArray(pyarr, reqDescr, 
                                                                NPY_ARRAY_FARRAY);
    if (cleanArr == NULL) throw std::bad_alloc();
    reqDescr = NULL;  // The new reference from DescrFromType was stolen by FromArray

    double* dataPtr = static_cast<double*>(PyArray_DATA(cleanArr));
    arma::mat result (dataPtr, dims[0], dims[1], true);  // this copies the data from cleanArr
    Py_DECREF(cleanArr);
    return result;
}

问题是,当我传递NumPy数组的这个视图时(即my_array[:, 3]),它似乎不能正确地处理底层C数组的跨步。根据输出,函数接收的数组pyarr实际上是完整的基数组,而不是视图(或者至少当我使用PyArray_DATA访问数据时,我似乎得到了一个指向完整基数组的指针)。如果我给这个函数传递一个视图的副本(即my_array[:, 3].copy()),它会按预期工作,但我不想每次都记住这样做。在

那么,有没有办法让PyArray_FromArray只复制我想要的矩阵的切片?我试着使用标志NPY_ARRAY_ENSURECOPY,但没用。在

编辑1

正如评论中所建议的,下面是一个完整的工作示例:

在文件example.cpp中:

^{pr2}$

setup.py用于编译:

from setuptools import setup, Extension
import numpy as np

example_module = Extension(
    'example',
    include_dirs=[np.get_include(), '/usr/local/include'],
    libraries=['armadillo'],
    library_dirs=['/usr/local/lib'],
    sources=['example.cpp'],
    language='c++',
    extra_compile_args=['-std=c++11', '-mmacosx-version-min=10.10'],
    )

setup(name='example',
      ext_modules=[example_module],
      )

现在假设我们有一个示例数组

a = np.array([[ 1, 2, 3, 4, 5, 6], 
              [ 7, 8, 9,10,11,12], 
              [13,14,15,16,17,18]], dtype='float64')

该函数对于多维切片(如a[:, :3])似乎可以很好地工作,并且它像我预期的那样原封不动地返回矩阵。但是如果我给它一个一维切片,除非我复制一个,否则我得到了错误的组件:

>>> example.test_function(a[:, 3])
array([ 4.,  5.,  6.])

>>> example.test_function(a[:, 3].copy())
array([  4.,  10.,  16.])

Tags: theifexample数组arrayintstdthrow
1条回答
网友
1楼 · 发布于 2024-09-30 18:33:36

数组视图只是同一数据数组的另一个信息包装器。Numpy不在此处复制任何数据。仅调整用于解释数据的信息,如果有用,则移动指向数据的指针。在

在您的代码中,假设向量a[:, 3]的数据表示为内存中的一个向量,对于NPY_ARRAY_CARRAY和{}来说没有区别。但是这个表示只有在创建数组本身的(fortran排序的)副本之后才能得到。在

为了使其工作,我修改了您的convertPyArrayToArma()函数,以创建一个副本,即使它是一个向量:

template<typename outT>
static arma::Mat<outT> convertPyArrayToArma(PyArrayObject* pyarr, int nrows, int ncols)
{
    if (!checkPyArrayDimensions(pyarr, nrows, ncols)) throw WrongDimensions();

    int arrTypeCode;
    if (std::is_same<outT, uint16_t>::value) {
        arrTypeCode = NPY_UINT16;
    }
    else if (std::is_same<outT, double>::value) {
        arrTypeCode = NPY_DOUBLE;
    }
    else {
        throw NotImplemented();
    }

    PyArray_Descr* reqDescr = PyArray_DescrFromType(arrTypeCode);
    if (reqDescr == NULL) throw std::bad_alloc();
    PyArrayObject* cleanArr = (PyArrayObject*)PyArray_FromArray(pyarr, reqDescr, NPY_ARRAY_FARRAY);
    if (cleanArr == NULL) throw std::bad_alloc();
    reqDescr = NULL;  // The new reference from DescrFromType was stolen by FromArray

    const auto dims = getPyArrayDimensions(pyarr);
    outT* dataPtr = static_cast<outT*>(PyArray_DATA(cleanArr));

    // this copies the data from cleanArr
    arma::Mat<outT> result;
    if (dims.size() == 1) {
        result = arma::Col<outT>(dataPtr, dims[0], true);
    }
    else {
        result = arma::Mat<outT>(dataPtr, dims[0], dims[1], true);
    }

    Py_DECREF(cleanArr);
    return result;
}

相关问题 更多 >