Cython:从参考获得时,Numpy 数组缺少两个第一个元素

Posted

技术标签:

【中文标题】Cython:从参考获得时,Numpy 数组缺少两个第一个元素【英文标题】:Cython: Numpy array missing two first elements when obtained from reference 【发布时间】:2018-01-15 12:31:22 【问题描述】:

这是最奇怪的错误,我试图从一个返回 reference 向量的 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&lt;int&gt; 而不是对 vector&lt;int&gt; 的引用,则代码会正常工作。

知道为什么会这样吗?

编辑:最终解决方案

将向量作为参数传递。

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)
)

【问题讨论】:

您的 c++ 代码返回对局部变量的引用,这几乎是必然的灾难。可能有一个关于它的编译器警告...... 您的函数应该是 const std::vector&lt;int&gt;&amp; get_vec() const return vec;std::vector&lt;int&gt;&amp; get_vec() return vec;std::vector&lt;int&gt; get_vec()return vec;,具体取决于您想要实现的目标,但不返回对临时对象的引用,正如 DavidW 指出的那样。 你是对的!我通过在.pyx 文件中声明向量并将引用作为参数传递来解决了这个问题。还有一个问题:np.asarray 是在创建数组中数据的副本吗?理想情况下,我想避免复制数据。 np.asarray 仅在需要时复制。你可以很容易地自己检查——写下相关的内存地址,看看它们是否匹配。 它肯定会复制,而且可能会复制两次(首先复制到 Python 列表,然后复制到数组) 【参考方案1】:

您的主要目标似乎是让 numpy 使用在 C++ 向量中分配的内存。为此,您最好为IntVec 实现缓冲区协议。 Cython 文档提供了一个 Matrix class based around a vector 的示例,您可以对其进行简化(因为您的案例只有一维)。您真正需要做的就是创建函数__getbuffer____releasebuffer__(后者可以是空白,如示例文档中所示)。 (我不认为在此处复制/粘贴文档有很大的价值)

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

【讨论】:

以上是关于Cython:从参考获得时,Numpy 数组缺少两个第一个元素的主要内容,如果未能解决你的问题,请参考以下文章

将 3D numpy 数组从 cython 传递到 C++

如何正确地将 numpy 数组传递给 Cython 函数?

Cython 优化 numpy 数组求和的关键部分

将带有字符串的结构化 numpy 数组传递给 cython 函数

Cython 中 numpy 数组掩码的性能

如何通过 cython 将 numpy 数组列表传递给 c++