在此我要特别感谢pybind11项目创立者 Wenzel Jakob以及众多项目参与者
5面向对象的代码
5.1创建自定义类型的绑定
现在来看一个更复杂的例子,这个例子为名为Pet的自定义数据结构创建绑定。
Pet定义如下:
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;
};
Pet的绑定如下:
#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);
}
class_为C ++类或结构体类型的数据结构创建绑定。init()将构造函数的参数类型作为模板参数并包装相应的构造函数(详细信息请参阅“ 自定义构造函数”部分)。
示例演示如下:
% 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'
See also: 静态成员函数可以使用class_::def_static()同样也能绑定。
5.2关键字和默认参数
可以使用前一章中讨论的语法来指定关键字和默认参数。详细信息,请参阅关键字参数和默认参数部分。
5.3绑定lambda函数
请注意上面示例中print(p)是如何生成一个无用的数据结构摘要信息:
>>> print(p)
<example.Pet object at 0x10cd98060>
为了解决这个问题,我们可以绑定一个名为__repr__的函数,该函数返回人可读的摘要。不幸的是,Pet数据结构中没有相应的功能,如果我们不必更改Pet的机构将会更好。这些可以通过绑定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 + "'>";
}
);
pybind11支持无状态[1]和有状态lambda闭包。通过上述更改,相同的Python代码生成以下输出:
>>> print(p)
<example.Pet named 'Molly'>
[1]无状态闭包是那些带有一对空括号[]
作为捕获对象的闭包
5.4实例和静态字段
我们也可以使用class::def_readwrite()来直接暴露name字段。类似的class::def_readonly适用于const字段。
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>())
.def_readwrite("name", &Pet::name)
// ... remainder …
这使得写入成为可能
>>> p = example.Pet('Molly')
>>> p.name
u'Molly'
>>> p.name = 'Charly'
>>> p.name
u'Charly'
现在假设这Pet::name 是一个私有的内部变量,只能通过setter和getters访问。
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;
};
在这种情况下,方法class_::def_property()(class_::defproperty_readonly()用于只读数据)可用于提供类似于Python中字段的接口,可以透明地调用setter和getter函数:
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>())
.def_property("name", &Pet::getName, &Pet::setName)
// ... remainder ...
只写属性可以通过传递nullptr给读取函数作为输入来定义。
See also:
class_::def_readwrite_static(),class_::def_readonly_static(),class_::def_property_static(),以及class_::def_property_readonly_static()为绑定静态变量和属性提供了类似功能。另请参阅文档高级部分中静态属性的部分。
5.5动态属性
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()显式定义的属性。
py::class_<Pet>(m, "Pet")
.def(py::init<>())
.def_readwrite("name", &Pet::name);
尝试设置任何其他属性会导致错误:
>>> p = example.Pet()
>>> p.name = 'Charly' # OK, attribute defined in C++
>>> p.age = 2 # fail
AttributeError: 'Pet' object has no attribute 'age'
C++类要启用动态属性,必须将py::dynamic_attr这个标记添加到py::class_构造函数中:
py::class_<Pet>(m, "Pet", py::dynamic_attr())
.def(py::init<>())
.def_readwrite("name", &Pet::name);
现在一切都按预期工作:
>>> p = example.Pet()
>>> p.name = 'Charly' # OK, overwrite value in C++
>>> p.age = 2 # OK, dynamically add a new attribute
>>> p.__dict__ # just like a native Python class
{'age': 2}
请注意,具有动态属性的类会有一点运行的时间成本。不仅因为添加了__dict__,还因为垃圾回收跟踪比较耗时,运行时必须要激活它。默认情况下,Python类也会产生相同的耗时,所以可以不用担心。默认情况下,pybind11的类比Python类更有效。启用动态属性只会使它们运行效率差不多,而不会超过。
5.6继承和自动转换
示例包含两个具有继承关系的数据结构:
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的层次关系:第一种是指定C++基类作为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);
第二种,我们也可以为之前绑定的Pet class_对象指定名称,并在绑定Dog类时引用它
py::class_<Pet> pet(m, "Pet");
pet.def(py::init<const std::string &>())
.def_readwrite("name", &Pet::name);
// 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);
功能方面,两种方法都是等价的。实例将公开两种类型的字段和方法:
>>> p = example.Dog('Molly')
>>> p.name
u'Molly'
>>> p.bark()
u'woof!'
上面定义的C++类是具有继承关系的常规非多态类型。这反映在Python中:
// Return a base pointer to a derived instance
m.def("pet_store", []() { return std::unique_ptr<Pet>(new Dog("Molly")); });
>>> p = example.pet_store()
>>> type(p) # `Dog` instance behind `Pet` pointer
Pet # no pointer downcasting for regular non-polymorphic types
>>> p.bark()
AttributeError: 'Pet' object has no attribute 'bark'
该函数返回了一个Dog实例,但因为它是基类指针指向的派生类型,所以Python只能看到Pet。在C++中,如果一个类型包含至少一个虚函数,那么被认为是多态的,pybind11会自动识别它:
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); });
>>> p = example.pet_store2()
>>> type(p)
PolymorphicDog # automatically downcast
>>> p.bark()
u'woof!'
给定指向多态基类的指针,pybind11执行自动转换为派生类型。请注意,这超出了C++中的通常情况:不只是访问基类的虚函数,还可以获得具体的派生类,这包括一些基类未声明过的函数和属性。
See also: 有关多态的更多信息,请参阅在Python中覆盖虚函数。
5.7重载方法
有时会有几个具有相同名称的C++方法,这些方法采用不同的输入参数类型:
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;
};
尝试绑定Pet::set将导致错误,因为编译器不知道应该指定哪种方法。我们可以通过将它们转换为函数指针来消除歧义。将多个函数绑定到相同的名称会自动创建一系列函数重载。
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &, int>())
.def("set", (void (Pet::*)(int)) &Pet::set, "Set the pet's age")
.def("set", (void (Pet::*)(const std::string &)) &Pet::set, "Set the pet's name");
重载签名也可以在方法的说明中看到:
>>> help(example.Pet)
class Pet(__builtin__.object)
| Methods defined here:
|
| __init__(...)
| Signature : (Pet, str, int) -> NoneType
|
| set(...)
| 1. Signature : (Pet, int) -> NoneType
|
| Set the pet's age
|
| 2. Signature : (Pet, str) -> NoneType
|
| Set the pet's name
如果你有一个兼容C++ 14的编译器[2],你可以使用另一种语法来转换重载函数:
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::*)()的干扰。如果函数基于常量重载的,则应使用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_));
[2]支持-std=c++14
标志或Visual Studio 2015 Update 2及更高版本的编译器。
Note: 要定义多个重载的构造函数,只需使用.def(py::init<...>())语法一个一个地声明。用于指定关键字和默认参数的机制也有效。
Note: 要定义多个重载的构造函数,只需使用.def(py::init<...>())语法一个一个地声明。用于指定关键字和默认参数的机制也有效。
5.8枚举和内部类型
示例中的类包含内部枚举类型,例如:
struct Pet {
enum Kind {
Dog = 0,
Cat
};
Pet(const std::string &name, Kind type) : name(name), type(type) { }
std::string name;
Kind type;
};
此示例的绑定代码如下所示:
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);
py::enum_<Pet::Kind>(pet, "Kind")
.value("Dog", Pet::Kind::Dog)
.value("Cat", Pet::Kind::Cat)
.export_values();
要确保Kind在Pet作用域创建,pet class_必须将实例传递给enum_构造函数。enum_::export_values()函数将枚举条目导出到父作用域中,对于较新的C ++ 11样式的强类型枚举,应该跳过该操作。
>>> p = Pet('Lucy', Pet.Cat)
>>> p.type
Kind.Cat
>>> int(p.type)
1L
枚举类型定义的条目在__members__属性中公开:
>>> Pet.Kind.__members__
{'Dog': Kind.Dog, 'Cat': Kind.Cat}
name属性将枚举值的名称作为unicode字符串返回。
Note: 也可以使用str(enum),但是这达到了不同的目的。以下显示了这两种方法的不同之处。
>>> 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会创建一个枚举,该枚举支持基本算术和位级操作,如比较、and、or、xor、否等。
py::enum_<Pet::Kind>(pet, "Kind", py::arithmetic())
...