pybind11中文资料(第五章 面向对象的代码)

在此我要特别感谢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 是一个私有的内部变量,只能通过settergetters访问。

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中字段的接口,可以透明地调用settergetter函数:

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())
   ...

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值