Skip to content

【Pybind11】01数据类型转换

finish time: 2025-05-23 10: 48

python和cpp之间直接转换的数据类型

在pybind11中,Python和C++之间的数据类型转换非常灵活,支持多种常见类型的直接映射。以下是可直接转换的主要数据类型及其注意事项:

1. 基本数据类型

  • 自动转换(无需额外头文件):
  • 整型intint(Python 3无long,统一为int)。
  • 浮点型float/doublefloat
  • 布尔型boolboolTrue/False)。
  • 字符串
    • C++ → Python:std::stringconst char*str
    • Python → C++:strstd::string(自动编码处理)。

2. 标准库容器

需包含 #include <pybind11/stl.h>: - 序列容器: - std::vector<T>list(元素类型T需可转换)。 - std::array<T, N>list/tuple(固定长度)。 - std::list<T>std::deque<T>list。 - 关联容器: - std::map<K, V>dict(键值类型需可转换)。 - std::set<T>set。 - 元组std::tuple<...>tuple。 - 其他std::optional<T>None或对应类型。

3. 第三方库与特殊类型

  • NumPy数组(需包含 #include <pybind11/numpy.h>):
  • pybind11::array_t<T>numpy.ndarray
  • 支持Eigen矩阵(需额外包含 pybind11/eigen.h)。
  • 缓冲区协议:可直接访问内存(如std::vector.data())。
  • 智能指针
  • std::shared_ptr<T> ↔ Python对象(自动管理引用计数)。
  • std::unique_ptr<T> ↔ Python对象(所有权转移)。

4. 自定义类型

通过 pybind11::class_ 绑定:

pybind11::class_<MyClass>(m, "MyClass")
    .def(pybind11::init<>())
    .def_readwrite("data", &MyClass::data)
    .def("method", &MyClass::method);
- C++对象 ↔ Python类实例。 - 支持继承、虚函数覆盖等高级特性。

5. 函数与回调

  • C++函数 → Python可调用对象:自动包装。
  • Python函数 → C++回调:
  • 使用 pybind11::function 类型。
  • 或绑定到 std::function(需包含 pybind11/functional.h)。

6. 其他类型

  • 枚举enum ↔ Python enum.Enum(需显式导出)。
  • 异常:C++异常 → Python异常(通过 pybind11::register_exception)。
  • 文件对象:Python文件类对象 ↔ C++ std::FILE*(需手动处理)。

注意事项

  1. 自动转换限制
  2. 嵌套容器(如 std::vector<std::map<int, string>>)需确保所有层级类型可转换。
  3. 非标准类型(如自定义结构体)需显式绑定。
  4. 性能优化
  5. 避免频繁转换大型容器,考虑使用 pybind11::buffer 或内存视图。
  6. 使用 std::move 避免不必要拷贝。
  7. 头文件依赖
  8. 标准容器转换需 #include <pybind11/stl.h>
  9. Eigen/NumPy支持需额外头文件。

示例代码

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

namespace py = pybind11;

// 绑定函数示例
int add(int a, int b) { return a + b; }

// 自定义类
struct MyClass { int value; };

PYBIND11_MODULE(example, m) {
    m.def("add", &add);

    py::class_<MyClass>(m, "MyClass")
        .def(py::init<>())
        .def_readwrite("value", &MyClass::value);
}

在Python中可直接使用:

import example
obj = example.MyClass()
obj.value = 42
print(example.add(1, 2))  # 输出 3

scipy与eigen之间的稀疏矩阵数据类型的自动转换解决方案

在pybind11中,NumPy/SciPy的稀疏矩阵类型与Eigen的稀疏矩阵类型之间默认不支持自动转换,但可以通过手动提取数据并构造对应格式的矩阵实现互通。以下是具体分析和解决方案:

1. 默认支持情况

  • pybind11官方库
  • 仅支持密集矩阵的自动转换(如Eigen::MatrixXdnumpy.ndarray),需包含pybind11/eigen.h
  • 稀疏矩阵的转换需要手动处理,因存储格式复杂(如CSR/CSC涉及数据、行、列索引等)。
  • SciPy稀疏矩阵
  • SciPy的稀疏矩阵(如csr_matrix/csc_matrix)并非NumPy原生类型,需通过scipy.sparse模块操作。

2. 手动转换方法

(1) SciPy稀疏矩阵 → Eigen稀疏矩阵

假设Python传递一个scipy.sparse.csr_matrix到C++,需提取其数据、行索引、列指针,并在C++端构造Eigen::SparseMatrix

Python端

import scipy.sparse as sp

# 创建一个CSR格式稀疏矩阵
csr_mat = sp.csr_matrix([[1, 0, 2], [0, 3, 0]])
indices = csr_mat.indices
indptr = csr_mat.indptr
data = csr_mat.data
shape = csr_mat.shape

# 将数据传递给C++函数
cpp_module.process_sparse(data, indices, indptr, shape)

C++端(绑定代码):

#include <Eigen/Sparse>
#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

void process_sparse(
    py::array_t<double> data,
    py::array_t<int> indices,
    py::array_t<int> indptr,
    std::pair<size_t, size_t> shape
) {
    // 映射为Eigen稀疏矩阵(CSR格式)
    Eigen::Map<const Eigen::SparseMatrix<double>> eigen_sparse(
        shape.first, shape.second,
        indptr.size() - 1,  // 非零元素数
        data.data(), indices.data(), indptr.data()
    );
    // 使用eigen_sparse进行计算...
}

PYBIND11_MODULE(example, m) {
    m.def("process_sparse", &process_sparse);
}

(2) Eigen稀疏矩阵 → SciPy稀疏矩阵

在C++端构造Eigen::SparseMatrix后,返回其数据、索引和指针数组,Python端用它们重建csr_matrix

C++端

py::dict get_sparse_data(const Eigen::SparseMatrix<double>& mat) {
    mat.makeCompressed();  // 确保压缩格式
    return py::dict(
        "data"_a = py::array_t<double>(mat.nonZeros(), mat.valuePtr()),
        "indices"_a = py::array_t<int>(mat.nonZeros(), mat.innerIndexPtr()),
        "indptr"_a = py::array_t<int>(mat.outerSize() + 1, mat.outerIndexPtr()),
        "shape"_a = std::make_pair(mat.rows(), mat.cols())
    );
}

Python端

import scipy.sparse as sp

data_dict = cpp_module.get_sparse_data()
csr_mat = sp.csr_matrix(
    (data_dict["data"], data_dict["indices"], data_dict["indptr"]),
    shape=data_dict["shape"]
)

3. 注意事项

  • 存储格式一致性
  • Eigen默认使用压缩列存储(CSC),而SciPy的csr_matrix压缩行存储(CSR),需注意格式匹配。
  • 若格式不匹配,需手动转置或转换(如通过Eigen的.transpose()或SciPy的.tocsr()/.tocsc())。
  • 内存管理
  • 直接映射原始指针(如Eigen::Map)可避免数据拷贝,但需确保Python端数据生命周期足够长。
  • 对于大矩阵,推荐传递只读(const)视图或拷贝数据。

4. 第三方扩展库

  • pybind11_eigen
  • 提供更丰富的Eigen类型支持,但对稀疏矩阵仍无自动转换。
  • 自定义封装工具
  • 可编写通用转换函数模板,支持多种稀疏格式(如CSR/CSC/COO)。

5. 示例代码(完整流程)

C++绑定模块

#include <Eigen/Sparse>
#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

// 处理SciPy CSR矩阵的C++函数
void process_sparse(
    py::array_t<double> data,
    py::array_t<int> indices,
    py::array_t<int> indptr,
    std::pair<size_t, size_t> shape
) {
    Eigen::Map<const Eigen::SparseMatrix<double>> mat(
        shape.first, shape.second,
        data.size(),  // 非零元素数
        data.data(), indices.data(), indptr.data()
    );
    // 执行稀疏矩阵运算...
}

// 返回Eigen稀疏矩阵数据的函数
py::dict get_eigen_sparse() {
    Eigen::SparseMatrix<double> mat(2, 3);
    mat.insert(0, 0) = 1;
    mat.insert(0, 2) = 2;
    mat.insert(1, 1) = 3;
    mat.makeCompressed();

    return py::dict(
        "data"_a = py::array_t<double>(mat.nonZeros(), mat.valuePtr()),
        "indices"_a = py::array_t<int>(mat.nonZeros(), mat.innerIndexPtr()),
        "indptr"_a = py::array_t<int>(mat.outerSize() + 1, mat.outerIndexPtr()),
        "shape"_a = std::make_pair(mat.rows(), mat.cols())
    );
}

PYBIND11_MODULE(sparse_utils, m) {
    m.def("process_sparse", &process_sparse);
    m.def("get_eigen_sparse", &get_eigen_sparse);
}

Python端使用

import numpy as np
import scipy.sparse as sp
from sparse_utils import process_sparse, get_eigen_sparse

# 将SciPy稀疏矩阵传递给C++
csr_mat = sp.csr_matrix([[1, 0, 2], [0, 3, 0]])
process_sparse(csr_mat.data, csr_mat.indices, csr_mat.indptr, csr_mat.shape)

# 从C++获取Eigen稀疏矩阵并转为SciPy格式
data_dict = get_eigen_sparse()
eigen_csr = sp.csr_matrix(
    (data_dict["data"], data_dict["indices"], data_dict["indptr"]),
    shape=data_dict["shape"]
)

总结

  • 默认不支持自动转换,需手动提取稀疏矩阵的数据、索引和指针数组。
  • 格式兼容性是关键,需统一使用CSR或CSC格式以避免额外转换。
  • 通过合理封装,可实现高效、无拷贝的稀疏矩阵交互。

要实现一个C++函数,该函数接受一个Eigen的CSC格式稀疏矩阵,将其值加倍,然后返回加倍后的稀疏矩阵,并通过pybind11与SciPy的csc_matrix交互,我们需要以下步骤:

Pybind11中csc稀疏矩阵在python和eigen库之间的数据传递解决方案

1. C++函数实现

功能函数 (double_sparse_matrix.cpp)

#include <Eigen/Sparse>
#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

// 通用转换函数:SciPy CSC矩阵 ↔ Eigen SparseMatrix
template <typename Scalar>
Eigen::SparseMatrix<Scalar> scipy_csc_to_eigen(
    py::array_t<Scalar> data,
    py::array_t<int> indices,
    py::array_t<int> indptr,
    int rows,
    int cols
) {
    // 确保输入是CSC格式
    Eigen::Map<const Eigen::SparseMatrix<Scalar>> eigen_sparse(
        rows, cols,
        data.size(),  // 非零元素数
        data.data(), indices.data(), indptr.data()
    );
    return eigen_sparse;
}

template <typename Scalar>
py::dict eigen_to_scipy_csc(const Eigen::SparseMatrix<Scalar>& mat) {
    // 确保矩阵是压缩列存储(CSC)格式
    Eigen::SparseMatrix<Scalar> csc_mat = mat;
    csc_mat.makeCompressed();

    return py::dict(
        "data"_a = py::array_t<Scalar>(
            {csc_mat.nonZeros()},  // Shape
            {sizeof(Scalar)},     // Stride
            csc_mat.valuePtr()     // 数据指针
        ),
        "indices"_a = py::array_t<int>(
            {csc_mat.nonZeros()},
            {sizeof(int)},
            csc_mat.innerIndexPtr()
        ),
        "indptr"_a = py::array_t<int>(
            {csc_mat.outerSize() + 1},
            {sizeof(int)},
            csc_mat.outerIndexPtr()
        ),
        "shape"_a = py::make_tuple(csc_mat.rows(), csc_mat.cols())
    );
}

// 主函数:将稀疏矩阵的值加倍
template <typename Scalar>
py::dict double_sparse_matrix(
    py::array_t<Scalar> data,
    py::array_t<int> indices,
    py::array_t<int> indptr,
    int rows,
    int cols
) {
    // 将SciPy CSC矩阵转为Eigen格式
    Eigen::SparseMatrix<Scalar> sm = scipy_csc_to_eigen<Scalar>(
        data, indices, indptr, rows, cols
    );

    // 加倍所有非零元素
    sm.coeffs() *= 2;

    // 转回SciPy CSC格式
    return eigen_to_scipy_csc(sm);
}

// 绑定到Python
PYBIND11_MODULE(sparse_ops, m) {
    m.def("double_sparse_matrix", &double_sparse_matrix<double>,
        py::arg("data"), py::arg("indices"), py::arg("indptr"),
        py::arg("rows"), py::arg("cols"),
        "Double the values of a CSC sparse matrix."
    );
}

2. 编译为Python模块

setup.py 配置

from setuptools import setup, Extension
import pybind11
import os

# 编译配置
ext_module = Extension(
    'sparse_ops',
    sources=['double_sparse_matrix.cpp'],
    include_dirs=[
        pybind11.get_include(),
        pybind11.get_include(True),  # Eigen路径需手动指定或通过环境变量
        '/usr/include/eigen3'        # 替换为你的Eigen路径
    ],
    language='c++',
    extra_compile_args=['-std=c++11'],
)

setup(
    name='sparse_ops',
    ext_modules=[ext_module],
)

运行编译命令:

python setup.py build_ext --inplace

3. Python端调用

测试脚本 (test_sparse_ops.py)

import scipy.sparse as sp
import numpy as np
from sparse_ops import double_sparse_matrix

# 创建一个SciPy CSC稀疏矩阵
csc_mat = sp.csc_matrix([[1, 0, 3], [0, 4, 0], [5, 0, 6]], dtype=np.float64)

# 提取CSC矩阵的数据、索引和指针
data = csc_mat.data
indices = csc_mat.indices
indptr = csc_mat.indptr
rows, cols = csc_mat.shape

# 调用C++函数
result = double_sparse_matrix(data, indices, indptr, rows, cols)

# 重建SciPy CSC矩阵
doubled_csc = sp.csc_matrix(
    (result["data"], result["indices"], result["indptr"]),
    shape=result["shape"]
)

print("Original matrix:\n", csc_mat.toarray())
print("Doubled matrix:\n", doubled_csc.toarray())

输出

Original matrix:
 [[1 0 3]
 [0 4 0]
 [5 0 6]]
Doubled matrix:
 [[ 2  0  6]
 [ 0  8  0]
 [10  0 12]]

4. 关键点说明

  1. 格式一致性
  2. 强制使用CSC格式,避免格式混淆(Eigen默认压缩列存储,与SciPy的csc_matrix一致)。
  3. 通过makeCompressed()确保矩阵数据是压缩的。

  4. 内存零拷贝

  5. 使用Eigen::Map直接映射SciPy的原始数据指针,避免拷贝。
  6. Python端的numpy.ndarray和C++的Eigen::SparseMatrix共享内存。

  7. 通用模板

  8. 支持任意标量类型(如float/double),通过模板参数Scalar实现。

  9. 异常处理

  10. 可添加try-catch块捕获非法输入(如非CSC格式数据)。