Cython:从参考获得时缺少两个前元素的 Numpy 数组

Cython: Numpy array missing two first elements when obtained from reference

本文关键字:两个 元素 数组 Numpy 参考 Cython      更新时间:2023-10-16
这是

最奇怪的错误,我试图从返回对向量的引用的 c++ 函数中获取一个 numpy 数组,整个使用 Cython 包装。

我可以让它返回vector<int>而不是vector<int>&,但我想了解使用引用时发生了什么。这是重现错误的一种方法:

CMYCLASS.h

#ifndef MYCLASS_H
#define MYCLASS_H
#include <vector>
#include <string>
namespace vec {
class IntVector {
    private:
        std::vector<int> vec;
    public:
        IntVector();
        virtual ~IntVector();
        std::vector<int>& get_vec(); #return a reference !
};
}
#endif

cmyclass.cc

#include "cmyclass.h"
#include <iostream>
using namespace vec;
IntVector::IntVector(){
    for(int i=10; i<20; ++i){
        vec.push_back(i);
    }
}
IntVector::~IntVector(){
}
std::vector<int>& IntVector::get_vec(){
    std::vector<int> buff;
    buff.reserve(vec.size());
    for(int i=0; i<vec.size(); ++i){
        buff.push_back(vec[i]);
    }
    return buff;
}

myclass.pyx

import numpy as np
cimport numpy as np
from libcpp.vector cimport vector
cdef extern from "cmyclass.h" namespace "vec":
    cdef cppclass IntVector:
        IntVector() except +
        vector[int]& get_vec()
cdef class IntVec:
    cdef IntVector* _thisptr
    def __cinit__(self):
        self._thisptr = new IntVector()
    def __dealloc__(self):
        del self._thisptr
    def __init__(self):
        pass  
    def get_vec(self):
        cdef vector[int] buff;
        buff = self._thisptr.get_vec();
        return np.asarray(buff)

setup.py

from distutils.core import setup
from Cython.Build import cythonize
from distutils.extension import Extension
sourcefiles  = ['myclass.pyx', 'cmyclass.cc']
compile_opts = ['-std=c++11']
ext=[Extension('*',
            sourcefiles,
            extra_compile_args=compile_opts,
            language='c++')]
setup(
  ext_modules=cythonize(ext)
)

您可以使用python setup.py build_ext --inplace进行编译

用例

>>> import myclass
>>> vec = myclass.IntVec()
>>> vec.get_vec()
array([ 0,  0, 12, 13, 14, 15, 16, 17, 18, 19])

您可以看到前两个值设置为零(它们应该是 10 和 11(!如果我们返回一个vector<int>而不是对vector<int>的引用,代码将正常工作。

知道为什么会这样吗?

编辑:最终解决方案

将向量作为参数传递。

CMYCLASS.h

#ifndef MYCLASS_H
#define MYCLASS_H
#include <vector>
#include <string>
namespace vec {
class IntVector {
    private:
        std::vector<int> vec;
    public:
        IntVector();
        virtual ~IntVector();
        void get_vec(std::vector<int>&);
};
}
#endif

cmyclass.cc

#include "cmyclass.h"
#include <iostream>
using namespace vec;
IntVector::IntVector(){
    for(int i=10; i<20; ++i){
        vec.push_back(i);
    }
}
IntVector::~IntVector(){
}
void IntVector::get_vec(std::vector<int>& buff){
    buff.reserve(vec.size());
    for(int i=0; i<vec.size(); ++i){
        buff.push_back(vec[i]);
    }
    return buff;
}

myclass.pyx

import numpy as np
cimport numpy as np
from libcpp.vector cimport vector
cdef extern from "cmyclass.h" namespace "vec":
    cdef cppclass IntVector:
        IntVector() except +
        void get_vec(vector[int]&)
cdef class IntVec:
    cdef IntVector* _thisptr
    def __cinit__(self):
        self._thisptr = new IntVector()
    def __dealloc__(self):
        del self._thisptr
    def __init__(self):
        pass  
    def get_vec(self):
        cdef vector[int] buff;
        self._thisptr.get_vec(buff);
        return np.asarray(buff)

setup.py

from distutils.core import setup
from Cython.Build import cythonize
from distutils.extension import Extension
sourcefiles  = ['myclass.pyx', 'cmyclass.cc']
compile_opts = ['-std=c++11']
ext=[Extension('*',
            sourcefiles,
            extra_compile_args=compile_opts,
            language='c++')]
setup(
  ext_modules=cythonize(ext)
)

您的主要目标似乎是让 numpy 使用在C++向量中分配的内存。为此,您可能更好地实现IntVec的缓冲区协议。Cython 文档提供了一个基于向量的矩阵类示例,您可以简化该向量(因为您的情况只有 1D(。您真正需要做的就是创建函数__getbuffer____releasebuffer__(后者可以是空白的,如示例文档中所示(。(我认为在这里复制/粘贴文档没有巨大的价值(

这样做将允许您将IntVec直接传递给np.asarray。生成的 numpy 数组将使用IntVec进行存储,并保留对IntVec的引用,以确保不会删除它。你也可以在此类中使用 Cython memoryviews(如果这有帮助的话(。