【问题标题】:Getting buffer of data into C++ with boost::python使用 boost::python 将数据缓冲区导入 C++
【发布时间】:2018-05-10 21:32:58
【问题描述】:

我希望能够将数值数据的缓冲区(即实现缓冲区协议的东西,例如 numpy 数组)从 Python 传递到 c++:

>>> import mymod
>>> import numpy
>>> mymod.some_func(numpy.array([1,2,3]))

并以某种方式在 c++ 中接收它:

void some_func([something] array) {
    for (int ii : array) {
       cout << ii << endl;
    }
}

打印

1 
2
3

我并不关心 [something] 是什么(指针、std::vector 等等)。有谁知道如何做到这一点?令人惊讶的是,关于它的信息很少......

【问题讨论】:

    标签: python c++ boost boost-python


    【解决方案1】:

    好的&lt;get_ready_for_this.mp3&gt;这是我为解决这个问题所做的。

    首先,我创建了一个表示我想要的缓冲区的类型,以及一些帮助函数将数据缓冲区转换为目标格式。您可以轻松地将其修改为更灵活,但我只想要一个复杂的浮点值数组。

    // vector of complex values
    typedef vector<cfloat> cbuffer;
    
    
    // helper to copy data
    template<typename T>
    void cbuffer_copy_from(cbuffer& cbuf, void *ptr, ssize_t len, ssize_t stride) {
        cbuf.reserve(len);
    
        // convert elements into buffer
        char* cptr = (char*)ptr;
        for (ssize_t ii=0; ii < len; ii++) {
            cbuf.emplace_back(*reinterpret_cast<T*>(cptr));
            cptr += stride;
        }
    };
    
    
    // populate vector from source
    template<typename T>
    void cbuffer_from(cbuffer& cbuf, void *ptr, ssize_t len, ssize_t stride) {
        cbuffer_copy_from<T>(cbuf, ptr, len, stride);
    }
    
    
    // fast path for data that's already cfloat
    template <>
    void cbuffer_from<cfloat>(cbuffer& cbuf, void *ptr, ssize_t len, ssize_t stride) {
        // if stride is right, we can just copy the data
        if (stride == sizeof(cfloat)) {
            cbuf.resize(len);
            memcpy(&cbuf[0], ptr, len*sizeof(cfloat));
        } else {
            cbuffer_copy_from<cfloat>(cbuf, ptr, len, stride);
        }
    }
    

    然后,我构建了一个从 python 到我的 cbuffer 类型的自定义转换器:

    // python -> cbuffer conversion
    struct python_to_cbuffer {
        // register converter 
        python_to_cbuffer() {
            converter::registry::push_back(
                &convertible,
                &construct,
                type_id<cbuffer>()
            );
        }
    
        // does python object implement buffer protocol?
        static void* convertible(PyObject* object) {
            return PyObject_CheckBuffer(object) ? object : nullptr;
        }
    
        // convert object into a complex number
        static void construct(
            PyObject* object,
            converter::rvalue_from_python_stage1_data* data
        ) {
            // grab pointer to memory into which to construct the new value
            void* storage = ((converter::rvalue_from_python_storage<cbuffer>*)data)->storage.bytes;
    
            // create buffer object from export source, require format
            Py_buffer view;
            if (PyObject_GetBuffer(object, &view, PyBUF_FORMAT | PyBUF_STRIDES) < 0) {
                return;
            }
    
            // make sure it's a one dimensional array
            if (view.ndim != 1) {
                PyBuffer_Release(&view);
                throw std::runtime_error("Array object is not one dimensional");
            }
    
            // build new cbuffer to store data
            new (storage) cbuffer;
            cbuffer* buffer = static_cast<cbuffer*>(storage);
    
            // try to convert view data into cfloat format
            string type(view.format);
                 if (type == "f")  cbuffer_from<float>  (*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "d")  cbuffer_from<double> (*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "Zf") cbuffer_from<cfloat> (*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "Zd") cbuffer_from<cdouble>(*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "b")  cbuffer_from<int8_t> (*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "h")  cbuffer_from<int16_t>(*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "i")  cbuffer_from<int32_t>(*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "l")  cbuffer_from<int32_t>(*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "q")  cbuffer_from<int32_t>(*buffer, view.buf, view.shape[0], view.strides[0]);
            else if (type == "n")  cbuffer_from<ssize_t>(*buffer, view.buf, view.shape[0], view.strides[0]);
            else {
                buffer->~cbuffer();
                throw std::runtime_error("Unable to marshall '" + string(view.format) + "' data format");
            }
    
            // Stash the memory chunk pointer for later use by boost.python
            data->convertible = storage;
        }
    };
    

    convertible() 函数检查 Python 对象是否实现了缓冲区协议。然后construct() 函数实际上从对象中提取一个缓冲区,并通过上述辅助函数将其转换为所需的格式。如果我们在任何步骤中失败,请清理并抛出运行时异常。

    最后我们在模块中实例化转换器:

    // define python module
    BOOST_PYTHON_MODULE(module) {
        // register python -> c++ converters
        python_to_cbuffer();
    
        def("test", test);
    }
    

    而且,如果我们创建一个测试函数:

    void test(cbuffer buf) {
        for (cfloat val : buf) {
            printf("(%f, %f)\n", val.re, val.im);
        }
    }
    

    然后在python中:

    >>> module.test(numpy.array([1+2j,3+4j],dtype=numpy.complex64))
    (1.000000, 2.000000)
    (3.000000, 4.000000)
    >>> module.test(numpy.array([1,2],'b'))
    (1.000000, 0.000000)
    (2.000000, 0.000000)
    >>> module.test(numpy.array([1,2],'i'))
    (1.000000, 0.000000)
    (2.000000, 0.000000)
    >>> module.test(numpy.array([1,2],'l'))
    (1.000000, 0.000000)
    (2.000000, 0.000000)
    

    享受吧!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-08
      • 2019-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多