pybind11 ---- a Python C++ Binding Library
文章目录
概述
类似 Boost.Python, 不过去掉了对 Boost 库的依赖,更轻便,便于绑定到 python
模块创建
例子
1 2 3 4 5 6 7 8 9 10 11#include <pybind11/pybind11.h> int add(int i, int j) { return i + j; } PYBIND11_MODULE(example, m) { m.doc() = "pybind11 example plugin"; // optional module docstring m.def("add", &add, "A function which adds two numbers"); }重点
使用 PYBIND11_MODULE 宏函数
宏函数接口
模块名指定
- 注意,不是字符串
宏函数 body
- 说明模块本身信息
相关对象
pybind11 命名空间
| |
- 使用惯例
模块类
py::module_
- PYBIND11_MODULE 宏函数的内部形参 m
方法
m.def()
- py::module_::def()
- 绑定函数
m.doc() = str
module_::doc() = "pybind11 example module doctring"- 设置模块文档
m.attr("variable_name") = …
- 定义模块级变量
py::object
- 即 Python object 对应类型
py::list
- Python list 类型
类型转换
- 即 python 类型 和 C++ 类型转换
- 大部分类型,pybind11 可以自动完成转换
- 参考:Type conversions — pybind11 documentation
相关函数
py::cast
- 强制类型转换
eg:
1py::object world = py::cast("World")- python 等价:
m.world = "World"
- python 等价:
_module::def 函数绑定
相关语句
1using namespace pybind11::literals;- "_a"
位置参数 –> 传递参数名称
用 py::arg 或 _a
1 2 3 4 5// regular notation m.def("add1", &add, py::arg("i"), py::arg("j")); // shorthand using namespace pybind11::literals; m.def("add2", &add, "i"_a, "j"_a);python 等价: add(i, j)
- 让 python 探知到 第一、第二个参数的名称就是 "i"、"j"
默认参数
- py::arg("i") = default_param
eg:
1 2 3 4 5// regular notation m.def("add1", &add, py::arg("i") = 1, py::arg("j") = 2); // shorthand m.def("add2", &add, "i"_a=1, "j"_a=2);
函数文档
- 在 m.def(…., "docstring"),最后一个参数指定 docstring 文档
eg:
1class_.def("set", &Pet::set, "Set the pet's age")
lambda 函数绑定
| |
绑定策略 return_value_policy
作用
规定函数返回值,所有权处置问题
- python 所有还是 C++ 所有,意味着 引用计数的处理方式
- 数据使用完成后的清理问题
特殊场景
Python 清理了 C++ 静态区对象
eg:
1 2 3 4 5 6 7 8 9/* Function declaration */ // _data 指向 C++ 静态存储区 Data *get_data() { return _data; /* (pointer to a static data structure) */ } ... /* Binding code */ // return_value_policy::automatic --> 对于指针pointer 变成 ::take_ownership, // python 会 调用 delete() ===> 导致错误析构 ==> 程序崩溃 m.def("get_data", &get_data); // <-- KABOOM, will cause crash when called from Python- 正确做法:
m.def("get_data", &get_data, py::return_value_policy::reference);
- 正确做法:
return_value_policy 枚举值
return_value_policy::take_ownership
- Python 负责
适用对象
- heap 区 指针 pointer
安全性
危险
- 多次析构
- 未初始化
return_value_policy::copy
- Python 负责
适用对象
lvalue
- 引用等
安全性
安全
- python 端获取的是全新拷贝
return_value_policy::move
- Python 负责
适用范围
rvalue
- 右值
安全性
安全
- 全新拷贝(python 端)
return_value_policy::reference
- C++ 负责
安全性
危险
- C++ 端已经析构,Python 还在使用
return_value_policy::reference_internal
类似 reference policy
- 但是有 keep_alive<0, 1> call policy 增强
引用还依赖 this 或 self 对象的存在
- 即:返回值 delete 前,this 或 self 绝对不允许被 先 delete 回收
安全性
- 比 reference policy 高
- def_property 和 def_readwrite 等的默认 policy
return_value_policy::automatic
py::class_::def()默认 policy处理方式
pointer -> take_ownership
- 潜在危险: pointer 可能指向不可被析构的对象,如静态区对象
- lvalue, 引用 -> copy
- rvale -> move
return_value_policy::automatic_reference
- 非面向用户 policy
def_property, def_readwrite 等默认 policy
- return_value_policy::reference_internal
py::class_::def() 默认 policy
- return_value_policy::automatic
注意事项
指定 policy 只适用于,pybind11 第一次见到这个对象
- 通过 类型 和 内存地址判断
- return_value_policy::reference 用法要参考 call policies
可以通过 smart pointer 自动完成 引用计数问题
参考:
- 返回 smart pointers 时,可以不用指定 return_value_policy
调用策略 call policy
- 和 return_value_policy 一起使用
作用
- 指定函数调用时参数间的依赖关系
代码例子
- pybind11 官方单元测试:tests/test_call_policies.cpp
keep_alive()
接口
keep_alive<Nurse, Patient>()
- Nurse 销毁后,Patient 才可以被销毁
- 即:存活时间 Nurse <= Patient
参数
说明
- 整型
- 参数 >= 1
- 0 用于表示返回值
1
- this 指针
>= 2
- 函数参数表
使用案例
list.append
eg:
1 2py::class_<List>(m, "List") .def("append", &List::append, py::keep_alive<1, 2>());- 1 -> this -> 容器 list
- 2 -> item -> 子部件
寿命: 容器 list <= item
- 即容器不在了,但是 item 依然存在,容器只是对 item 引用了一次
call_guard<T>
参考
- 一种<类 Deamon 工具>,快捷调用工具
等价代码
1 2 3 4 5 6 7 8// 实际使用方式 m.def("foo", foo, py::call_guard<T>()); // 等价代码 m.def("foo", [](args...) { T scope_guard; return foo(args...); // forwarded arguments });使用案例
与 py::gil_scoped_release 一起使用
1 2 3 4 5 6 7 8m.def("call_go", [](Animal *animal) -> std::string { /* Release GIL before calling into (potentially long-running) C++ code */ py::gil_scoped_release release; return call_go(animal); }); // call_guard 实现方式 m.def("call_go", &call_go, py::call_guard<py::gil_scoped_release>());
_module::attr() 属性处理
eg:
1 2 3 4 5PYBIND11_MODULE(example, m) { m.attr("the_answer") = 42; py::object world = py::cast("World"); m.attr("what") = world; }
面向对象 —- 类的处理
eg:
1 2 3 4 5 6 7 8 9 10#include <pybind11/pybind11.h> namespace py = pybind11; PYBIND11_MODULE(example, m) { py::class_<Pet>(m, "Pet") .def(py::init<const std::string &>()) .def("setName", &Pet::setName) .def("getName", &Pet::getName); }
类的声明
py::class_<CppClass, BaseClass>(m, "ClassNameInPython")
方法绑定 def()
- py::class_::def()
简单方法
py::class_.def("methodNameInPython", &CppClass::methodName)- 不涉及重载等
重载
实现方式
- 多条语句,对应多个重载方法
函数的类型转换,避免重名问题
C++ 11
- static_cast<函数指针类型>(&funcName)
- class_.def("overloadedMethod", static_cast<function_pointer_type>(&methodName))
C++ >= 14
- py::overload_cast<参数类型>(&funcName)
- class_.def("overloadedMethod", py::overload_cast<func_param_type_list>(&methodName))
C++ 11 使用 overload_cast 方法
1 2template <typename... Args> using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>;
eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23// ------------------ 重载函数定义 struct Pet { Pet(const std::string &name, int age) : name(name), age(age) { } void set(int age_) { age = age_; } void set(const std::string &name_) { name = name_; } std::string name; int age; }; // ------------------ 绑定重载函数 // C++11 py::class_<Pet>(m, "Pet") .def(py::init<const std::string &, int>()) .def("set", static_cast<void (Pet::*)(int)>(&Pet::set), "Set the pet's age") .def("set", static_cast<void (Pet::*)(const std::string &)>(&Pet::set), "Set the pet's name"); // C++ >= 14 py::class_<Pet>(m, "Pet") .def("set", py::overload_cast<int>(&Pet::set), "Set the pet's age") .def("set", py::overload_cast<const std::string &>(&Pet::set), "Set the pet's name");
构造函数重载
使用 class_.def(py::init<input_type_list>())
- 使用 py::init<param_type_list>()
- 类似普通方法的重载即可
eg:
1 2 3 4 5py::class_<Pet> pet(m, "Pet"); pet.def(py::init<>()) .def(py::init<const std::string &>()) .def_readwrite("name", &Pet::name);
lambda 函数
eg:
1 2 3 4pet.def("__repr__", [](const Pet &a) { return "<example.Pet named '" + a.name + "'>"; }接口规定
- 接口设定有 self 即可
注意
capture 为 []
- 不接受外部任何内容
字段绑定
公有字段
- .def_readwrite_*()
私有字段
- .def_property_*()
静态字段字段
公有:
- .def_readwrite_static()
私有
- .def_property_*_static()
公有实例字段 public: int field;
- 即:实例的 public 成员变量
- .def_readwrite("name", &Cppclass::field)
私有实例字段 private: int field;
绑定方法
- 绑定对应的 setter, getter
rw: 可读可写字段
- .def_property("name", &Cppclass::getter, &Cppclass::setter)
r: 只读字段
- .def_property_readonly("name", &Cppclass::getter)
w: 只写字段
- .def_property("name", nullptr, &Cppclass::setter)
公有静态字段 public: static int field
- .def_readwrite_static()
私有静态字段 private: static int field;
rw:
- .def_property_static()
r:
- .def_property_readonly_static()
w:
- .def_property_static("name", nullptr, &Cppclass::setter)
例子 —- static private 私有静态字段
说明
python 角度
- python 要求 类字段,没有 self, 但是要有一个 cls
- 参考:static-properties
实现方式
- 修改 cpp 文件,单做一个接口第一个参数是 (py::object cls) 的 getter, setter
- eg: public: static getCount(py::object cls) {return count;}
c++ 角度
静态字段 初始化
static const 类型: 在类内部直接初始化
eg:
1 2 3 4 5class Demo{ private: // 立刻初始化 static const int id = 1000; }
static non-const 类型: 在类外部(通常是 cpp 文件中)初始化
eg:
1 2 3 4 5 6 7class Demo { private: static int count; // 相当于 extern, 可以放到头文件中,多处出现 }; // 外部初始化, cpp 文件中,唯一一处,不会重复初始化 int Demo::count = 0;
静态字段 读取,设置
- static getter(), setter() 添加 py::object 参数
实战
cpp 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61#include<pybind11/pybind11.h> namespace py = pybind11; class Demo{ private: static int score; public: // 用于 c++ 内部使用 static int getScore() {return score;} static void setScore(int _score) {score = _score;} // 用于 python 绑定 static int getScore(py::object cls) {return score;} static void setScore(py::object cls, int new_score) {score = new_score;} }; int Demo::score = 100; PYBIND11_MODULE(demo, m){ py::class_<Demo>(m, "Demo") .def_property_readonly_static("ro_score", py::overload_cast<py::object>(&Demo::getScore)) .def_property_static("score", py::overload_cast<py::object>(&Demo::getScore), py::overload_cast<py::object, int>(&Demo::setScore)); } /* ,* # 测试结果 In [3]: import demo In [4]: help(demo) In [5]: demo.Demo.score Out[5]: 100 In [6]: demo.Demo.score = 100 In [7]: demo.Demo.score = 200 In [8]: demo.Demo.score Out[8]: 200 In [9]: demo.Demo.ro_score Out[9]: 200 In [10]: demo.Demo.ro_score = 300 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-10-94247630f43e> in <module> ----> 1 demo.Demo.ro_score = 300 AttributeError: can't set attribute ,*/CMakeLists.txt
1 2 3 4 5 6 7 8cmake_minimum_required(VERSION 3.15) project(Demo_Pybind) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) find_package(pybind11 REQUIRED) pybind11_add_module(demo demo.cpp)
python 端 动态属性 —- 允许 Python 内部绑定属性
功能说明
- pybind11 默认不支持 python 动态绑定属性到 C++ 对象
eg:
- Dog() 类,在 C++ pybind11 绑定时,只有 name 没有 age
python code: dog.age = 11
- 非法,
raise AttributeError
- 非法,
hack 实现方法
- 声明绑定 C++ 类时,传入 py::dynamic_attr()
eg:
1 2 3py::class_<Pet>(m, "Pet", py::dynamic_attr()) .def(py::init<>()) .def_readwrite("name", &Pet::name);
继承
实现方法
- 先声明所有基类
再声明子类
两种语法
py::class_<ChildClass, ParentClass>(m, "name")
- 使用基类的 C++版本 –> ParentClass
py::class_<ChildClass>(m, "name", ParentClassInstance)
- 使用基类绑定版本,py::class_ –> ParentClassInstance
复写 —- 多态绑定 —- virtual 函数
类指针的多态
C++ 要求基类至少有一个 virtual 虚函数 才能拥有多态特性 pylymorphic
- 即 Pet* pet = new Dog()
- python 中,这里 pet 多态实现时, pet 内部存储的时 Dog 类型
没有虚函数方法的实现
- 使用 虚析构函数实现
eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17struct PolymorphicPet { // 虚函数--析构函数 virtual ~PolymorphicPet() = default; }; struct PolymorphicDog : PolymorphicPet { std::string bark() const { return "woof!"; } }; // Same binding code py::class_<PolymorphicPet>(m, "PolymorphicPet"); py::class_<PolymorphicDog, PolymorphicPet>(m, "PolymorphicDog") .def(py::init<>()) .def("bark", &PolymorphicDog::bark); // Again, return a base pointer to a derived instance m.def("pet_store2", []() { return std::unique_ptr<PolymorphicPet>(new PolymorphicDog); });
override 函数重写
参考
问题说明
C++ 的 对 virtual 函数 override, python 中不能正常绑定
- 基类因为是虚基类,所有不能创建实例,在 Python 中不能实现
实现方法
添加基类的辅助类
- 对所有的基类 virtual 函数添加宏函数修饰
eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18/* 制作帮助函数 */ class PyAnimal : public Animal { public: /* Inherit the constructors */ /* 继承构造函数*/ using Animal::Animal; /* Trampoline (need one for each virtual function) */ /* 宏函数修饰 */ std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE( std::string, /* Return type */ Animal, /* Parent class */ go, /* Name of function in C++ (must match Python name) */ n_times /* Argument(s) */ ); } };
声明绑定基类时,传入帮助类
eg:
1 2 3 4 5 6 7 8 9 10 11PYBIND11_MODULE(example, m) { // 在此处传入辅助类 - ↓ -, 即可 py::class_<Animal, PyAnimal /* <--- trampoline*/>(m, "Animal") .def(py::init<>()) .def("go", &Animal::go); // 注意绑定时,传入的还是真正的基类,不是辅助类 py::class_<Dog, Animal>(m, "Dog") .def(py::init<>()); m.def("call_go", &call_go); }
virtual 函数 复写辅助宏函数
PYBIND11_OVERRIDE_PURE
纯虚函数
- virtual … method(…) = 0;
PYBIND11_OVERRIDE(return_type, BaseClass, functionName, …param_type_list)
- 普通虚函数
PYBIND11_OVERRIDE_PURE_NAME
- 纯虚函数,允许修改对应 python 中的函数名
PYBIND11_OVERRIDE_NAME(return_type, BaseClass, nameInPython,functionName, …param_type_list)
- 普通虚函数,允许修改对应 python 中的函数名
PYBIND11_OVERRIDE_* 使用注意事项
- 参考:general-notes-regarding-convenience-macros
preprocessor 导致的问题
- 如果类型中包含 逗号“,”,预处理器解析宏发生错误
- eg: std::array<int, 3>
解决方法
方法一:使用类型别名 using alias = your_type;
eg:
1 2 3 4// Version 1: using a type alias using ReturnType = MyReturnType<T1, T2>; using ClassType = Class<T3, T4>; PYBIND11_OVERRIDE(ReturnType, ClassType, func);
方法二:使用 py::PYBIND11_TYPE(your_type) 包裹类型
eg:
1 2 3// Version 2: using the PYBIND11_TYPE macro: PYBIND11_OVERRIDE(PYBIND11_TYPE(MyReturnType<T1, T2>), PYBIND11_TYPE(Class<T3, T4>), func)
python 类 继承 C++ 类
在
__init__函数处理必须调用 ParentClass.__init__()
- 避免 TypeError 报错
不能使用 super(self).__init__()
- 避免 python MRO 和 C++ 机制不同问题
内部类型
绑定方式
- 当成普通的外部类型声明即可
注意
- py::class_<OuterClass::InnerClass>(outer_class_binder, "InPythonName")
- 注意这里的 parent 对象时 OuterClass
参考
eg:
嵌套类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16struct Pet { enum Kind { Dog = 0, Cat }; struct Attributes { float age = 0; }; Pet(const std::string &name, Kind type) : name(name), type(type) { } std::string name; Kind type; Attributes attr; };绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15py::class_<Pet> pet(m, "Pet"); pet.def(py::init<const std::string &, Pet::Kind>()) .def_readwrite("name", &Pet::name) .def_readwrite("type", &Pet::type) .def_readwrite("attr", &Pet::attr); py::enum_<Pet::Kind>(pet, "Kind") .value("Dog", Pet::Kind::Dog) .value("Cat", Pet::Kind::Cat) .export_values(); py::class_<Pet::Attributes> attributes(pet, "Attributes") .def(py::init<>()) .def_readwrite("age", &Pet::Attributes::age);
Enum 类型
声明 py::enum_
使用 py::enum_<CppEnum>(parent, "inPythonName")
parent
- 模块 m 或者 外部类 OuterClassBinder
绑定枚举值
- .value("nameInPython", &EnumItem)
hack 支持位运算
支持运算符
- or
- xor
- and
- supports rudimentary arithmetic and bit-level operations like comparisons, and, or, xor, negation, etc
- 声明时,传入 py::arithmetic()
eg:
1py::enum_<Pet::Kind>(pet, "Kind", py::arithmetic()) pet_kind;
例子
| |
文章作者
上次更新 2022-03-07 (de34a70)