如何在C++中生成可被Python pickle加载为numpy ndarray的字节数据
解决方案:C++生成可被Python pickle加载的numpy ndarray字节数据
问题概述
需要在C++中完成两个核心步骤:
- 将GSL的
gsl_vector和gsl_matrix转换为C++的ndarray结构; - 将该ndarray序列化为字节数据,确保Python的
pickle.loads能直接识别为numpy ndarray。
无需跨库的简化方案
方案1:用Boost.Python实现完整流程
Boost.Python原生提供ndarray实现和pickle序列化支持,无需依赖其他库,是最直接的解决方案:
步骤1:GSL容器转Boost ndarray
#include <boost/python/numpy.hpp> #include <gsl/gsl_vector.h> #include <gsl/gsl_matrix.h> #include <algorithm> namespace np = boost::python::numpy; // gsl_vector转Boost ndarray np::array gsl_vector_to_ndarray(const gsl_vector* vec) { np::dtype dtype = np::dtype::get_builtin<double>(); np::array arr = np::empty(vec->size, dtype); std::copy(vec->data, vec->data + vec->size, reinterpret_cast<double*>(arr.get_data())); return arr; } // gsl_matrix转Boost ndarray np::array gsl_matrix_to_ndarray(const gsl_matrix* mat) { np::dtype dtype = np::dtype::get_builtin<double>(); boost::python::tuple shape = boost::python::make_tuple(mat->size1, mat->size2); np::array arr = np::empty(shape, dtype); double* arr_data = reinterpret_cast<double*>(arr.get_data()); for (size_t i = 0; i < mat->size1; ++i) { std::copy(gsl_matrix_row(mat, i)->data, gsl_matrix_row(mat, i)->data + mat->size2, arr_data + i * mat->size2); } return arr; }
步骤2:Boost ndarray序列化为pickle字节流
#include <boost/python.hpp> #include <vector> #include <string> std::vector<char> ndarray_to_pickle_bytes(np::array arr) { boost::python::object pickle = boost::python::import("pickle"); boost::python::object dumps = pickle.attr("dumps"); // 调用pickle.dumps,使用最高兼容协议版本 boost::python::str pickle_str = dumps(arr, boost::python::object(-1)); std::string str = boost::python::extract<std::string>(pickle_str); return std::vector<char>(str.begin(), str.end()); }
方案2:手动构造numpy pickle格式(无第三方库依赖)
如果不想依赖Boost,可以直接按照numpy的pickle协议构造字节数据,核心是存储数组的** dtype、形状、数据缓冲区**及numpy类标识:
#include <vector> #include <cstdint> std::vector<char> manual_ndarray_to_pickle(const double* data, size_t rows, size_t cols) { std::vector<char> pickle_data; // Pickle协议头(协议4,兼容Python3.4+) pickle_data.push_back(0x80); pickle_data.push_back(0x04); // 引用numpy.ndarray类 pickle_data.insert(pickle_data.end(), "cnumpy.core.multiarray\nndarray\nq\x00", 29); // 构造__reduce__返回的元组结构 pickle_data.push_back(0x85); pickle_data.insert(pickle_data.end(), "q\x01cnumpy.core.multiarray\n_reconstruct\nq\x02", 36); pickle_data.push_back(0x8c); pickle_data.push_back(0x01); pickle_data.push_back(0x94); // 写入形状元组 pickle_data.push_back(0x85); pickle_data.insert(pickle_data.end(), "K", 1); pickle_data.push_back(rows); pickle_data.insert(pickle_data.end(), "K", 1); pickle_data.push_back(cols); pickle_data.push_back(0x94); // 写入dtype(float64小端) pickle_data.insert(pickle_data.end(), "X\x08\x00\x00\x00<f8\x94", 8); // 写入存储顺序(C行优先) pickle_data.insert(pickle_data.end(), "X\x01\x00\x00\x00C\x94", 6); // 写入原始数据 const char* data_bytes = reinterpret_cast<const char*>(data); pickle_data.insert(pickle_data.end(), data_bytes, data_bytes + rows*cols*sizeof(double)); // 结束标记 pickle_data.push_back(0x94); pickle_data.push_back(0x87); pickle_data.push_back(0x94); pickle_data.push_back(0x2e); return pickle_data; }
关于Boost转PicklingTools Val的说明
如果一定要用PicklingTools,需要手动将Boost ndarray的属性映射为Val结构:创建DictVal模拟numpy.ndarray的shape(TupleVal)、dtype(StrVal)、data(BytesVal)等关键属性,再用DumpValToArray序列化。但这种方式需要额外处理numpy类的标识映射,步骤繁琐且容易出错,完全没必要,优先推荐前两种方案。
内容的提问来源于stack exchange,提问作者FV_at_Alst
相关产品推荐
相关产品推荐

