ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Notes: Python pybind11 import C++ module

Notes: Python pybind11 import C++ module

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


📦 基础框架:创建 Python 模块

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

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

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


🧩 暴露普通函数

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

intadd(inta,intb){returna+b;}PYBIND11_MODULE(example,m){m.def("add",&add,"A function that adds two numbers");// Lambda 同样支持m.def("multiply",[](doublex,doubley){returnx*y;});}
命名参数与默认值

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

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

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

voidprocess(intx){/* ... */}voidprocess(conststd::string&s){/* ... */}m.def("process",py::overload_cast<int>(&process));m.def("process",py::overload_cast<conststd::string&>(&process));

🏗️ 暴露类与结构体

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

structPet{Pet(conststd::string&name):name(name){}voidsetName(conststd::string&name_){name=name_;}conststd::string&getName()const{returnname;}std::string name;};PYBIND11_MODULE(example,m){py::class_<Pet>(m,"Pet").def(py::init<conststd::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::arraylist
std::set,std::unordered_setset
std::map,std::unordered_mapdict
std::pair,std::tupletuple
#include<pybind11/stl.h>std::vector<int>double_elements(conststd::vector<int>&v){std::vector<int>result;for(intx:v)result.push_back(x*2);returnresult;}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_的第二个模板参数传入。

structPolymorphicPet{virtual~PolymorphicPet()=default;virtualstd::stringspeak()const{return"...";}};structDog:PolymorphicPet{std::stringspeak()constoverride{return"Woof!";}std::stringfetch()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>{returnstd::make_unique<Dog>();});

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


⚠️ 异常处理

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

C++ 异常Python 异常
std::invalid_argumentValueError
std::out_of_rangeIndexError
std::bad_allocMemoryError
std::exception(兜底)RuntimeError
voidvalidate(intx){if(x<0)throwstd::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。

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帮忙整理。

返回列表