pybind11绑定类(一)
一、自定义数据结构-结构体
`class_`会创建C++ class或 struct的绑定。`init()`方法使用类构造函数的参数类型作为模板参数,并包装相应的构造函数;静态成员函数需要使用`class_::def_static`来绑定
#include <pybind11/pybind11.h> namespace py = pybind11; 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); } python >>> import example >>> p = example.Pet("Molly") >>> print(p) <example.Pet object at 0x10cd98060> >>> p.getName() u'Molly' >>> p.setName("Charly") >>> p.getName() u'Charly'
二、绑定匿名函数
使用`print(p)`打印对象信息时,上面的例子会得到一些基本无用的信息,可以绑定一个工具函数到`__repr__`方法,来返回可读性好的摘要信息。在不改变Pet类的基础上,使用一个匿名函数来完成。pybind11支持无状态和有状态的lambda闭包,即lambda表达式的`[]`是否带捕获参数。
py::class_<Pet>(m, "Pet") .def(py::init<const std::string &>()) .def("setName", &Pet::setName) .def("getName", &Pet::getName) .def("__repr__", [](const Pet &a) { return "<example.Pet named '" + a.name + "'>"; }); python >>> print(p) <example.Pet named 'Molly'>
三、访问成员变量
使用`class_::def_readwrite`方法可以导出公有成员变量,使用`class_::def_readonly`方法则可以导出只读成员。
py::class_<Pet>(m, "Pet") .def(py::init<const std::string &>()) .def_readwrite("name", &Pet::name) python >>> p = example.Pet("Molly") >>> p.name u'Molly' >>> p.name = "Charly" >>> p.name u'Charly'
假设`Pet::name`是一个私有成员变量,向外提供setter和getters方法。可以使用`class_::def_property()`(只读成员使用`class_::def_property_readonly()`)来定义并私有成员,并生成相应的setter和geter方法:
class Pet { public: Pet(const std::string &name) : name(name) { } void setName(const std::string &name_) { name = name_; } const std::string &getName() const { return name; } private: std::string name; }; py::class_<Pet>(m, "Pet") .def(py::init<const std::string &>()) .def_property("name", &Pet::getName, &Pet::setName)
四、动态属性
原生的Pyhton类可以动态地获取新属性:
```python
>>> class Pet:
... name = "Molly"
...
>>> p = Pet()
>>> p.name = "Charly" # overwrite existing
>>> p.age = 2 # dynamically add a new attribute
```
默认情况下,从C++导出的类不支持动态属性,其可写属性必须是通过`class_::def_readwrite`或`class_::def_property`定义的。试图设置其他属性将产生错误。要让C++类也支持动态属性,我们需要在`py::class_`的构造函数添加`py::dynamic_attr`标识:
py::class_<Pet>(m, "Pet", py::dynamic_attr()) .def(py::init<>()) .def_readwrite("name", &Pet::name);
五、继承与向下转型
现在有两个具有继承关系的类:
struct Pet { Pet(const std::string &name) : name(name) { } std::string name; }; struct Dog : Pet { Dog(const std::string &name) : Pet(name) { } std::string bark() const { return "woof!"; } };
pybind11提供了两种方法来指明继承关系:1)将C++基类作为派生类`class_`的模板参数;2)将基类名作为`class_`的参数绑定到派生类。两种方法是等效的。
py::class_<Pet>(m, "Pet") .def(py::init<const std::string &>()) .def_readwrite("name", &Pet::name); // Method 1: template parameter: py::class_<Dog, Pet /* <- specify C++ parent type */>(m, "Dog") .def(py::init<const std::string &>()) .def("bark", &Dog::bark); // Method 2: pass parent class_ object: py::class_<Dog>(m, "Dog", pet /* <- specify Python parent type */) .def(py::init<const std::string &>()) .def("bark", &Dog::bark);
六、多态类型
```c++ struct 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); }); ``` ```python >>> p = example.pet_store2() >>> type(p) PolymorphicDog # automatically downcast >>> p.bark() u'woof!' ```
pybind11会自动地将一个指向多态基类的指针,向下转型为实际的派生类类型。不仅可以访问基类的虚函数,还能获取到通过基类看不到的,具体的派生类的方法和属性。
七、函数重载
重载方法即拥有相同的函数名,但入参不一样的函数:
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; };
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");
`py::overload_cast`仅需指定函数类型,不用给出返回值类型,以避免原语法带来的不必要的干扰(`void (Pet::*)`)。如果是基于const的重载,需要使用`py::const`标识。
struct Widget { int foo(int x, float y); int foo(int x, float y) const; }; py::class_<Widget>(m, "Widget") .def("foo_mutable", py::overload_cast<int, float>(&Widget::foo)) .def("foo_const", py::overload_cast<int, float>(&Widget::foo, py::const_));
如果你想在仅支持c++11的编译器上使用`py::overload_cast`语法,可以使用`py::detail::overload_cast_impl`来代替:
template <typename... Args> using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>; py::class_<Pet>(m, "Pet") .def("set", overload_cast_<int>()(&Pet::set), "Set the pet's age") .def("set", overload_cast_<const std::string &>()(&Pet::set), "Set the pet's name");
八、枚举类型
现在有一个含有枚举和内部类型的类:
struct 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; }; py::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);
枚举类型的枚举项会被导出到类`__members__`属性中:
python >>> Pet.Kind.__members__ {'Dog': Kind.Dog, 'Cat': Kind.Cat}
`name`属性可以返回枚举值的名称的unicode字符串,`str(enum)`也可以做到,但两者的实现目标不同。下面的例子展示了两者的差异:
python >>> p = Pet("Lucy", Pet.Cat) >>> pet_type = p.type >>> pet_type Pet.Cat >>> str(pet_type) 'Pet.Cat' >>> pet_type.name 'Cat'
Note: 当我们给`enum_`的构造函数增加`py::arithmetic()`标识时,pybind11将创建一个支持基本算术运算和位运算(如比较、或、异或、取反等)的枚举类型。