Notes: Python pybind11 import C++ module

pybind11 的核心用法是通过 PYBIND11_MODULE 宏创建一个 Python 扩展模块,然后使用 m.def() 暴露函数,py::class_ 暴露类,并借助头文件实现 STL 容器的自动转换。下面从基础到进阶,系统介绍如何暴露 C++ 接口。


📦 基础框架:创建 Python 模块

所有 pybind11 绑定都从 PYBIND11_MODULE 宏开始。它定义了模块名,并提供一个 py::module_ 对象(通常命名为 m),后续所有绑定操作都通过它进行。

cpp 复制代码
#include <pybind11/pybind11.h>
namespace py = pybind11;

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example module"; // 模块的文档字符串
    // 在此处暴露函数、类等
}

编译后,Python 中即可通过 import example 使用。


🧩 暴露普通函数

使用 m.def() 将 C++ 函数(或 Lambda)绑定到 Python 模块。

cpp 复制代码
int add(int a, int b) { return a + b; }

PYBIND11_MODULE(example, m) {
    m.def("add", &add, "A function that adds two numbers");
    // Lambda 同样支持
    m.def("multiply", [](double x, double y) { return x * y; });
}
命名参数与默认值

通过 py::arg() 指定参数名,并可用 = 设置默认值,使 Python 端支持关键字调用。

cpp 复制代码
m.def("add", &add, py::arg("a"), py::arg("b") = 10);
// Python 中可写 add(a=5) 或 add(5),b 默认为 10
函数重载

pybind11 会自动处理 C++ 重载函数,只需将不同的函数指针分别 def 到同一个 Python 名称即可,调用时会按参数类型自动匹配。

cpp 复制代码
void process(int x)    { /* ... */ }
void process(const std::string& s) { /* ... */ }

m.def("process", py::overload_cast<int>(&process));
m.def("process", py::overload_cast<const std::string&>(&process));

🏗️ 暴露类与结构体

py::class_ 是暴露自定义类型的核心,通过链式 .def() 绑定构造函数、方法和属性。

cpp 复制代码
struct Pet {
    Pet(const std::string &name) : name(name) {}
    void setName(const std::string &name_) { name = name_; }
    const std::string &getName() const { return name; }
    std::string name;
};

PYBIND11_MODULE(example, m) {
    py::class_<Pet>(m, "Pet")
        .def(py::init<const std::string &>())      // 构造函数
        .def("setName", &Pet::setName)              // 方法
        .def("getName", &Pet::getName)
        .def_readwrite("name", &Pet::name);         // 可读写属性
}
  • py::init<...>():绑定构造函数,模板参数即构造函数的参数类型。
  • def_readwrite / def_readonly:直接暴露 public 成员变量为 Python 属性。

对于私有构造函数或工厂模式,可用 py::init() 配合 Lambda 返回对象实例。


🔄 STL 容器的自动转换

只需额外包含头文件 <pybind11/stl.h>,pybind11 便会自动在以下 C++ 类型与 Python 内置容器之间进行转换:

C++ 类型 Python 类型
std::vector, std::list, std::deque, std::array list
std::set, std::unordered_set set
std::map, std::unordered_map dict
std::pair, std::tuple tuple
cpp 复制代码
#include <pybind11/stl.h>

std::vector<int> double_elements(const std::vector<int>& v) {
    std::vector<int> result;
    for (int x : v) result.push_back(x * 2);
    return result;
}
m.def("double_elements", &double_elements);
// Python: example.double_elements([1,2,3]) → [2,4,6]

⚠️ 注意 :自动转换涉及拷贝 ,会丧失 C++ 的引用语义,每次跨界都会复制整个容器,对大型数据有性能影响。若需零拷贝或引用传递,可考虑使用 pybind11/stl_bind.h 进行不透明绑定,或改用 NumPy 数组。


🧬 继承与多态

pybind11 能自动识别 C++ 的 virtual 函数,实现多态对象的自动向上转型(upcasting)。绑定派生类时,将基类作为 py::class_ 的第二个模板参数传入。

cpp 复制代码
struct PolymorphicPet {
    virtual ~PolymorphicPet() = default;
    virtual std::string speak() const { return "..."; }
};

struct Dog : PolymorphicPet {
    std::string speak() const override { return "Woof!"; }
    std::string fetch() const { return "Ball"; }
};

py::class_<PolymorphicPet>(m, "PolymorphicPet")
    .def("speak", &PolymorphicPet::speak);

py::class_<Dog, PolymorphicPet>(m, "Dog")
    .def(py::init<>())
    .def("fetch", &Dog::fetch);

// 返回基类指针时,若实际对象是 Dog,Python 端会得到 Dog 类型
m.def("make_pet", []() -> std::unique_ptr<PolymorphicPet> {
    return std::make_unique<Dog>();
});

在 Python 中,make_pet() 返回的对象类型为 Dog,可直接调用 fetch(),尽管 C++ 返回的是 PolymorphicPet*。这超越了标准 C++ 的虚函数机制,pybind11 会恢复完整的派生类型信息。


⚠️ 异常处理

pybind11 内置了 C++ 标准异常到 Python 异常的自动翻译,无需手动捕获。

C++ 异常 Python 异常
std::invalid_argument ValueError
std::out_of_range IndexError
std::bad_alloc MemoryError
std::exception(兜底) RuntimeError
cpp 复制代码
void validate(int x) {
    if (x < 0) throw std::invalid_argument("x must be non-negative");
}
m.def("validate", &validate);
// Python: example.validate(-1) → ValueError: x must be non-negative

自定义异常 :若需要将特定的 C++ 异常类映射为自定义 Python 异常,可使用 py::register_exception。

cpp 复制代码
py::register_exception<MyCppException>(m, "MyError", PyExc_RuntimeError);

此后从 C++ 抛出的 MyCppException 会在 Python 端以 MyError 的形式出现。


💡 补充要点

  • 智能指针 :pybind11 原生支持 std::shared_ptr 和 std::unique_ptr,返回智能指针时,Python 对象会自动管理其生命周期。pybind11 v3 起推荐使用 py::smart_holder 以获得更安全的默认行为。
  • NumPy 零拷贝 :包含 <pybind11/numpy.h> 后,py::array_t<T> 可与 NumPy 数组零拷贝交互,适合暴露大规模数值计算接口。
  • 枚举 :使用 py::enum_ 将 C++ enum 暴露给 Python,支持位运算和值转换。
  • GIL 管理 :在 C++ 中调用 Python 对象(如 py::object)时必须持有 GIL(全局解释器锁),pybind11 在大多数入口处会自动处理,但在纯 C++ 线程中回调 Python 需手动 py::gil_scoped_acquire。

暴露 C++ 接口的核心路径可以归纳为:m.def 绑函数 → py::class_ 绑类 → 按需引入 <pybind11/stl.h> 等头文件启用自动转换 → 用继承绑定实现多态 → 异常自动翻译。从最简单的自由函数开始,逐步扩展到类、容器和多态,即可覆盖绝大多数工程场景。

以上由 deepseek 帮忙整理。

相关推荐
王中阳Go2 小时前
后端转 Agent 岗面试怎么答:8 个高频问题与应答口径
人工智能·后端·python·大语言模型
Wang's Blog2 小时前
Java 项目实战: 交友项目 - 搜索附近的功能概述和分析
java·开发语言·交友
骑着蜗牛撵大象3272 小时前
大模型 Agent 的沙箱护栏:gVisor 隔离、出口白名单与审计闭环
java·开发语言·jvm·安全·agent沙箱·gvisor·microvm
weixin_307779133 小时前
RAG 面试陷阱——混合检索并非无条件优越:一个条件反转实验
人工智能·python·面试·职场和发展
Dxy12393102163 小时前
JS DES 加密详解
开发语言·javascript·arcgis
充值内卷3 小时前
我给桌面加了个鱼缸:C++ 写后端,WebView2 里养一座 Three.js 小岛
开发语言·javascript·c++
云和数据.ChenGuang3 小时前
深入解析 Python 的 dir():从用法到 CPython 源码实现
开发语言·人工智能·python·langchain
nice_lcj5204 小时前
深入理解 Java 反射:从 Class 对象到方法调用(含性能实测 + 迷你 IOC 实战 + 面试考点)
java·开发语言·后端·面试
荣码6 小时前
长上下文与大文档处理:200页PDF怎么喂给AI,我踩了4个坑
java·python