前置知识: C++

C++与Python交互

4 minIntermediate2026/6/14

pybind11与C API

概述

C++ 与 Python 交互是指在两种语言之间互相调用代码。Python 有丰富的生态和简洁的语法,但运行速度较慢;C++ 性能强大但开发效率较低。将两者结合,可以用 Python 快速编写业务逻辑,用 C++ 实现性能关键的部分。主要的交互方式有 Python C API(底层但灵活)和 pybind11(现代且易用)。

为什么需要 C++ 与 Python 交互?深度学习框架(PyTorch、TensorFlow)用 C++ 实现核心计算,用 Python 提供易用的接口;科学计算库(NumPy、SciPy)用 C/C++ 加速数值运算;游戏引擎用 C++ 处理渲染,用 Python 编写脚本逻辑。如果你需要在 Python 中调用 C++ 的高性能代码,或者需要在 C++ 中使用 Python 的库,就需要掌握两者之间的交互技术。

基础概念

Python C API:Python 提供的 C 语言接口,是最低层的交互方式。所有引用计数、型检查都需要手动处理,容易出错但最灵活。

pybind11:基于 C++11 的轻量级头文件库,简化了 C++ 与 Python 的绑定。自动处理引用计数、型转换和异常传递,是当前最推荐的方式。

引用计数:Python 使用引用计数管理内存。C API 中每次增加 Python 对象的引用时必须调用 Py_INCREF,减少时调用 Py_DECREF,否则会导致内存泄漏或崩溃。

GIL(全局解释器锁):Python 的全局解释器锁,同一时刻只允许一个线程执行 Python 代码。C++ 代码在调用 Python API 时必须持有 GIL,纯 C++ 计算可以释放 GIL 以实现并行。

快速上手

使用 pybind11

安装 pybind11:

pip install pybind11

编写 C++ 模块

// mymodule.cpp - 一个简单的 Python 扩展模块
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>  // 支持 STL 容器自动转换
#include <string>

namespace py = pybind11;

// 普通函数
int add(int a, int b) {
    return a + b;
}

// 带字符串参数的函数
std::string greet(const std::string& name) {
    return "你好, " + name + "!";
}

// 定义 Python 模块
PYBIND11_MODULE(mymodule, m) {
    m.doc() = "示例 Python 扩展模块";

    // 绑定函数
    m.def("add", &add, "两个数相加",
          py::arg("a"), py::arg("b"));

    m.def("greet", &greet, "问候函数",
          py::arg("name"));

    // 绑定带默认参数的函数
    m.def("multiply", [](int a, int b = 2) { return a * b; },
          "乘法运算", py::arg("a"), py::arg("b") = 2);
}

编译为 Python 模块

# 使用 pybind11 提供的编译工具
c++ -O3 -Wall -shared -std=c++17 -fPIC $(python3 -m pybind11 --includes) mymodule.cpp -o mymodule$(python3-config --extension-suffix)

或者使用 CMake:

cmake_minimum_required(VERSION 3.14)
project(mymodule)

find_package(pybind11 REQUIRED)

pybind11_add_module(mymodule mymodule.cpp)

在 Python 中使用:

import mymodule

result = mymodule.add(3, 5)
print(result)  # 8

greeting = mymodule.greet("世界")
print(greeting)  # 你好, 世界!

product = mymodule.multiply(4)     # 使用默认参数 b=2
print(product)  # 8

详细用法

绑定

#include <pybind11/pybind11.h>
#include <string>
#include <vector>

namespace py = pybind11;

class Pet {
public:
    Pet(const std::string& name, int age)
        : name_(name), age_(age) {}

    std::string getName() const { return name_; }
    void setName(const std::string& name) { name_ = name; }

    int getAge() const { return age_; }
    void setAge(int age) { age_ = age; }

    std::string info() const {
        return name_ + ", " + std::to_string(age_) + "岁";
    }

private:
    std::string name_;
    int age_;
};

PYBIND11_MODULE(petmodule, m) {
    // 绑定类
    py::class_<Pet>(m, "Pet")
        // 构造函数
        .def(py::init<const std::string&, int>(),
             py::arg("name"), py::arg("age"))

        // 绑定方法
        .def("info", &Pet::info, "获取宠物信息")

        // 绑定属性(通过 getter/setter)
        .def_property("name", &Pet::getName, &Pet::setName)
        .def_property("age", &Pet::getAge, &Pet::setAge)

        // 只读属性
        .def_property_readonly("description", &Pet::info)

        // 设置文档字符串
        .doc() = "宠物类";
}

Python 中使用:

import petmodule

pet = petmodule.Pet("旺财", 3)
print(pet.info())       # 旺财, 3岁
print(pet.name)         # 旺财
pet.name = "小花"
print(pet.name)         # 小花
pet.age = 4
print(pet.age)          # 4

继承与多态

class Animal {
public:
    virtual ~Animal() = default;
    virtual std::string speak() const = 0;
};

class Dog : public Animal {
public:
    std::string speak() const override { return "汪汪!"; }
};

class Cat : public Animal {
public:
    std::string speak() const override { return "喵喵!"; }
};

// 工厂函数
std::unique_ptr<Animal> createAnimal(const std::string& type) {
    if (type == "dog") return std::make_unique<Dog>();
    if (type == "cat") return std::make_unique<Cat>();
    throw std::runtime_error("未知动物类型: " + type);
}

PYBIND11_MODULE(animals, m) {
    // 绑定基类(注意要指定持有人类型以支持多态)
    py::class_<Animal>(m, "Animal")
        .def("speak", &Animal::speak);

    // 绑定派生类,指定基类
    py::class_<Dog, Animal>(m, "Dog")
        .def(py::init<>());

    py::class_<Cat, Animal>(m, "Cat")
        .def(py::init<>());

    // 绑定工厂函数
    m.def("create_animal", &createAnimal,
          py::return_value_policy::take_ownership);
}

STL 容器转换

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>  // 必须包含此头文件
#include <vector>
#include <map>

namespace py = pybind11;

// 使用 vector 的函数
double average(const std::vector<double>& numbers) {
    if (numbers.empty()) return 0.0;
    double sum = 0;
    for (double n : numbers) sum += n;
    return sum / numbers.size();
}

// 使用 map 的函数
std::map<std::string, int> wordCount(const std::vector<std::string>& words) {
    std::map<std::string, int> counts;
    for (const auto& word : words) {
        counts[word]++;
    }
    return counts;
}

PYBIND11_MODULE(stlmodule, m) {
    m.def("average", &average);
    m.def("word_count", &wordCount);
}

Python 中使用:

import stlmodule

# Python list 自动转换为 std::vector
avg = stlmodule.average([1.0, 2.0, 3.0, 4.0])
print(avg)  # 2.5

# 返回的 std::map 自动转换为 Python dict
counts = stlmodule.word_count(["hello", "world", "hello"])
print(counts)  # {'hello': 2, 'world': 1}

NumPy 交互

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>  // NumPy 支持
#include <vector>

namespace py = pybind11;

// 从 NumPy 数组读取数据
double sumArray(py::array_t<double> arr) {
    auto buf = arr.request();  // 获取缓冲区信息
    double* ptr = static_cast<double*>(buf.ptr);

    double sum = 0;
    for (ssize_t i = 0; i < buf.size; i++) {
        sum += ptr[i];
    }
    return sum;
}

// 创建 NumPy 数组返回给 Python
py::array_t<double> createArray(int size) {
    auto result = py::array_t<double>(size);
    auto buf = result.request();
    double* ptr = static_cast<double*>(buf.ptr);

    for (int i = 0; i < size; i++) {
        ptr[i] = static_cast<double>(i) * i;
    }

    return result;
}

// 不复制地操作 NumPy 数组(零拷贝)
void scaleArray(py::array_t<double> arr, double factor) {
    auto buf = arr.request();
    double* ptr = static_cast<double*>(buf.ptr);

    for (ssize_t i = 0; i < buf.size; i++) {
        ptr[i] *= factor;
    }
    // 直接修改原数组,无需返回
}

PYBIND11_MODULE(numpymodule, m) {
    m.def("sum_array", &sumArray);
    m.def("create_array", &createArray);
    m.def("scale_array", &scaleArray);
}

释放 GIL 进行并行计算

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

namespace py = pybind11;

// 释放 GIL 的纯计算函数
std::vector<double> heavyComputation(int size) {
    // 释放 GIL,允许其他 Python 线程运行
    py::gil_scoped_release release;

    std::vector<double> result(size);
    for (int i = 0; i < size; i++) {
        result[i] = std::sqrt(static_cast<double>(i)) * std::sin(i);
    }

    // release 对象析构时自动恢复 GIL
    return result;
}

// 在 C++ 中调用 Python 代码
void callPythonFromCpp() {
    // 获取 GIL
    py::gil_scoped_acquire acquire;

    py::module_ json = py::module_::import("json");
    py::object result = json.attr("dumps")(py::dict("name"_a = "张三", "age"_a = 25));
    std::string jsonStr = result.cast<std::string>();
    // jsonStr = '{"name": "张三", "age": 25}'
}

PYBIND11_MODULE(parallelmodule, m) {
    m.def("heavy_computation", &heavyComputation);
    m.def("call_python", &callPythonFromCpp);
}

异常处理

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

namespace py = pybind11;

// C++ 异常自动转换为 Python 异常
double divide(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("除数不能为零");
    }
    return a / b;
}

// 注册自定义异常类型
class MyError : public std::runtime_error {
public:
    MyError(const std::string& msg) : std::runtime_error(msg) {}
};

PYBIND11_MODULE(errormodule, m) {
    // 注册 C++ 异常到 Python 异常的映射
    py::register_exception<MyError>(m, "MyError");

    // std::runtime_error 自动映射为 Python RuntimeError
    m.def("divide", &divide);

    // 抛出自定义异常
    m.def("throw_error", []() { throw MyError("自定义错误"); });
}

常见场景

性能加速 Python 代码

// 用 C++ 加速 Python 中的排序算法
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <vector>
#include <algorithm>

namespace py = pybind11;

// C++ 实现的快速排序
void quickSort(std::vector<int>& arr, int left, int right) {
    if (left >= right) return;

    int pivot = arr[(left + right) / 2];
    int i = left, j = right;

    while (i <= j) {
        while (arr[i] < pivot) i++;
        while (arr[j] > pivot) j--;
        if (i <= j) {
            std::swap(arr[i], arr[j]);
            i++;
            j--;
        }
    }

    quickSort(arr, left, j);
    quickSort(arr, i, right);
}

std::vector<int> cppSort(std::vector<int> arr) {
    quickSort(arr, 0, static_cast<int>(arr.size()) - 1);
    return arr;
}

PYBIND11_MODULE(sortmodule, m) {
    m.def("cpp_sort", &cppSort, "C++ 快速排序");
}

注意事项

GIL 管理:C++ 代码调用 Python API 时必须持有 GIL。长时间纯 C++ 计算应释放 GIL,否则会阻塞所有 Python 线程。

内存管理:pybind11 自动管理 Python 对象的引用计数,但如果你直接使用 Python C API,必须手动管理。

编译兼容性:扩展模块必须用与 Python 解释器相同的编译器和 ABI 编译。不同版本的 Python 可能不兼容。

STL 头文件:使用 STL 容器转换时必须包含 <pybind11/stl.h>,否则编译会报错。

返回值策略:pybind11 有多种返回值策略(reference、copy、move、take_ownership 等),选择不当会导致悬垂指针内存泄漏

进阶用法

使用 Python C API

#include <Python.h>

// 不使用 pybind11,直接使用 C API
PyObject* cApiAdd(PyObject* self, PyObject* args) {
    int a, b;
    // 解析参数
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return nullptr;  // 解析失败,抛出 TypeError
    }
    // 返回结果
    return PyLong_FromLong(a + b);
}

// 方法定义表
static PyMethodDef methods[] = {
    {"add", cApiAdd, METH_VARARGS, "两个数相加"},
    {nullptr, nullptr, 0, nullptr}  // 哨兵
};

// 模块定义
static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "capi_module",     // 模块名
    "C API 示例模块",   // 文档
    -1,
    methods
};

// 模块初始化
PyMODINIT_FUNC PyInit_capi_module(void) {
    return PyModule_Create(&module);
}