如何从 boost::python 返回 numpy.array?

Posted

技术标签:

【中文标题】如何从 boost::python 返回 numpy.array?【英文标题】:how to return numpy.array from boost::python? 【发布时间】:2012-05-28 21:55:29 【问题描述】:

我想从 C++ 代码中返回一些数据作为 numpy.array 对象。我看过boost::python::numeric,但它的文档非常简洁。我可以举个例子吗?将(不是很大)vector<double> 返回给 python?我不介意复制数据。

【问题讨论】:

我同意它的文档很糟糕。他们只是将无注释的标题复制到他们的文档页面中,并没有向您展示基础知识,即从 STL 集合中获取数据到此对象中。 助推的人很聪明,太聪明了为自己好。我去了他们的 Wrapper 概念页面,没有看到任何有意义的东西。 我找到了我认为是我遇到过的最好的解决方案并将其发布在下面。 【参考方案1】:

更新:我的原始答案 (https://github.com/ndarray/Boost.NumPy) 中描述的库已直接集成到 Boost.Python 中,因为 Boost 1.63,因此现在不推荐使用独立版本。下面的文本现在对应于新的集成版本(只有命名空间发生了变化)。

Boost.Python 现在将 NumPy C-API 的适度完整包装器包含到 Boost.Python 接口中。这是相当低级的,主要集中在如何解决更困难的问题,即如何在不复制的情况下将 C++ 数据传入和传出 NumPy,但这里是你如何使用复制的 std::vector 返回:

#include "boost/python/numpy.hpp"

namespace bp = boost::python;
namespace bn = boost::python::numpy;

std::vector<double> myfunc(...);

bn::ndarray mywrapper(...) 
    std::vector<double> v = myfunc(...);
    Py_intptr_t shape[1] =  v.size() ;
    bn::ndarray result = bn::zeros(1, shape, bn::dtype::get_builtin<double>());
    std::copy(v.begin(), v.end(), reinterpret_cast<double*>(result.get_data()));
    return result;


BOOST_PYTHON_MODULE(example) 
    bn::initialize();
    bp::def("myfunc", mywrapper);

【讨论】:

如果我真的可以访问代码可能会非常好,但 github 似乎在这里被阻止了,或者因为我的链接断开而出现了其他问题。当然,必须有一种方法可以使用来自简单 std::vector 的数据填充 boost::python::numeric::array ,而无需获取一些第 3 方库。如果 boost 的文档实际上为您提供了有关成员函数的文档而不是复制未注释的标头,那将会有所帮助。 我无法进行编辑,因为它太小了,但应该是bn::zeros,而不是bp::zeros 我无法完成这项工作(Ubuntu 14.04)。 (...) 的例子是什么?bn::initialize() 应该做什么?此外,该示例似乎已过时-> 当我尝试包含 boost/numpy.hpp 时,我得到 fatal error: boost/numpy.hpp: No such file or directory 添加编译行会很有帮助,比如g++。至少这会提供有关链接的信息。【参考方案2】:

不需要您下载任何特殊的第 3 方 C++ 库(但您需要 numpy)的解决方案。

#include <numpy/ndarrayobject.h> // ensure you include this header

boost::python::object stdVecToNumpyArray( std::vector<double> const& vec )

      npy_intp size = vec.size();

     /* const_cast is rather horrible but we need a writable pointer
        in C++11, vec.data() will do the trick
        but you will still need to const_cast
      */

      double * data = size ? const_cast<double *>(&vec[0]) 
        : static_cast<double *>(NULL); 

    // create a PyObject * from pointer and data 
      PyObject * pyObj = PyArray_SimpleNewFromData( 1, &size, NPY_DOUBLE, data );
      boost::python::handle<> handle( pyObj );
      boost::python::numeric::array arr( handle );

    /* The problem of returning arr is twofold: firstly the user can modify
      the data which will betray the const-correctness 
      Secondly the lifetime of the data is managed by the C++ API and not the 
      lifetime of the numpy array whatsoever. But we have a simple solution..
     */

       return arr.copy(); // copy the object. numpy owns the copy now.
  

当然,您可以从 double * 和 size 编写一个函数,它是通用的,然后通过提取此信息从向量中调用它。您也可以编写一个模板,但您需要某种从数据类型到 NPY_TYPES 枚举的映射。

【讨论】:

感谢这个例子。只是提醒一下,我不得不使用 numeric::array::set_module_and_type("numpy", "ndarray");或者我会得到 python 运行时错误“ImportError: No module named 'Numeric' or its type 'ArrayType' did not follow the NumPy protocol” 感谢@PiQuer,它有帮助 如果您可以将参数设为非常量引用,为什么还要const_casting? @rubenvb 因为我们希望参数是一个常量引用。我们实际上并不打算修改数据,但我们需要解决 PyArray_SimpleNewFromData 需要 double* 的事实 请注意,与我在 *** 上的许多答案不同,这是我真正需要它的情况,来到这里,找到了问题但没有足够的答案。然后解决它并回来发布它。【参考方案3】:

有点晚了,但经过多次不成功的尝试后,我找到了一种将 c++ 数组直接公开为 numpy 数组的方法。这是一个使用 boost::python 和 Eigen 的简短 C++11 示例:

#include <numpy/ndarrayobject.h>
#include <boost/python.hpp>

#include <Eigen/Core>

// c++ type
struct my_type 
  Eigen::Vector3d position;
;


// wrap c++ array as numpy array
static boost::python::object wrap(double* data, npy_intp size) 
  using namespace boost::python;

  npy_intp shape[1] =  size ; // array size
  PyObject* obj = PyArray_New(&PyArray_Type, 1, shape, NPY_DOUBLE, // data type
                              NULL, data, // data pointer
                              0, NPY_ARRAY_CARRAY, // NPY_ARRAY_CARRAY_RO for readonly
                              NULL);
  handle<> array( obj );
  return object(array);




// module definition
BOOST_PYTHON_MODULE(test)

  // numpy requires this
  import_array();

  using namespace boost::python;

  // wrapper for my_type
  class_< my_type >("my_type")
    .add_property("position", +[](my_type& self) -> object 
        return wrap(self.position.data(), self.position.size());
      );


该示例描述了属性的“getter”。对于“setter”,最简单的方法是使用boost::python::stl_input_iterator&lt;double&gt;boost::python::object 手动分配数组元素。

【讨论】:

您能告诉我如何设置我的项目以使用 numpy 标头吗?我需要编译一些库吗?还是包含 numpy 标头就足够了? 我得到了 numpy 头目录使用:python -c "import numpy; print numpy.get_include()" 好的。那行得通,谢谢。但编译器抱怨 import_array() 正在返回一个值,而 init_module_... 是一个“void”函数。 好的,所以这似乎与 import_array() 宏如何从 Python 2 更改为 Python 3 到现在返回一些东西有关。这是一个保持版本独立的(丑陋的)解决方案:mail.scipy.org/pipermail/numpy-discussion/2010-December/… 显然,boost::python 现在提供对 numpy 数组的直接访问:boost.org/doc/libs/1_63_0/libs/python/doc/html/numpy/tutorial/… 无法链接:-/【参考方案4】:

直接使用 numpy api 并不一定很难,但我经常在我的项目中使用 boost::multiarray 并且发现在 C++/Python 边界之间自动传输数组的形状很方便。所以,这是我的食谱。使用http://code.google.com/p/numpy-boost/,或者更好的是,this numpy_boost.hpp 标头的版本;尽管它使用了一些 C++11,但它更适合多文件 boost::python 项目。 然后,从您的 boost::python 代码中,使用如下内容:

PyObject* myfunc(/*....*/)

   // If your data is already in a boost::multiarray object:
   // numpy_boost< double, 1 > to_python( numpy_from_boost_array(result_cm) );
   // otherwise:
   numpy_boost< double, 1> to_python( boost::extents[n] );
   std::copy( my_vector.begin(), my_vector.end(), to_python.begin() );

   PyObject* result = to_python.py_ptr();
   Py_INCREF( result );

   return result;

【讨论】:

返回py::object (py=boost::python) 的正确方法是什么?我有PyObject* result=numpy_boost&lt;double,2&gt;(numpy_from_boost_array(...)).py_ptr();return py::object(py::handle&lt;&gt;(py::borrowed(o))); 但崩溃了。提示? 附言。崩溃发生在 Dropbox 版本的第 229 行,a = (PyArrayObject*)PyArray_SimpleNew(NDims, shape, detail::numpy_type_map&lt;T&gt;::typenum); 行。奇怪。 @eudoxos 您可能对 PY_ARRAY_UNIQUE_SYMBOL 和 NO_IMPORT_ARRAY 宏以及 import_array 有问题,因为您的崩溃正是在创建数组时,需要通过某些指针表调用(我认为) numpy 需要(用 import_array() 初始化)。 C++11 版本的链接坏了。你介意解决这个问题吗?【参考方案5】:

我查看了可用的答案并想,“这很容易”。我继续花费数小时尝试看似微不足道的示例/答案改编。

然后我准确地实现了@max 的答案(必须安装 Eigen)并且它运行良好,但我仍然无法适应它。我的问题主要是(按数量)愚蠢的语法错误,但另外,在向量似乎从堆栈中删除后,我使用了指向复制的 std::vector 数据的指针。

在此示例中,返回指向 std::vector 的指针,但您也可以返回 size 和 data() 指针或使用任何其他实现,使您的 numpy 数组以稳定的方式访问基础数据(即保证存在):

class_<test_wrap>("test_wrap")
    .add_property("values", +[](test_wrap& self) -> object 
            return wrap(self.pvalues()->data(),self.pvalues()->size());
        )
    ;

对于带有std::vector&lt;double&gt; 的 test_wrap(通常 pvalues() 可能只返回指针而不填充向量):

class test_wrap 
public:
    std::vector<double> mValues;
    std::vector<double>* pvalues() 
        mValues.clear();
        for(double d_ = 0.0; d_ < 4; d_+=0.3)
        
            mValues.push_back(d_);
        
        return &mValues;
    
;

完整示例在 Github 上,因此您可以跳过繁琐的转录步骤,而不必担心构建、库等。您应该能够执行以下操作并获得一个正常运行的示例(如果您安装了必要的功能并且你的路径设置已经):

git clone https://github.com/ransage/boost_numpy_example.git
cd boost_numpy_example
# Install virtualenv, numpy if necessary; update path (see below*)
cd build && cmake .. && make && ./test_np.py

这应该给出输出:

# cmake/make output
values has type <type 'numpy.ndarray'>
values has len 14
values is [ 0.   0.3  0.6  0.9  1.2  1.5  1.8  2.1  2.4  2.7  3.   3.3  3.6  3.9]

*就我而言,我将 numpy 放入 virtualenv 如下 - 如果您可以按照 @max 的建议执行 python -c "import numpy; print numpy.get_include()",这应该是不必要的:

# virtualenv, pip, path unnecessary if your Python has numpy
virtualenv venv
./venv/bin/pip install -r requirements.txt 
export PATH="$(pwd)/venv/bin:$PATH"

玩得开心! :-)

【讨论】:

以上是关于如何从 boost::python 返回 numpy.array?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 Boost.Python 从 python 文件中导入函数

boost::python: PyErr_Fetch 总是返回 NULL 回溯

boost::python - 如何从 C++ 在自己的线程中调用 python 函数?

如何使用 Boost Python 从 C++ bool 转换为 Python boolean?

从boost python模块中的pyside导入类?

nump库的简单函数介绍