在此我要特别感谢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())...

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

  1. 第十五章 面向对象程序设计

    第十五章 面向对象程序设计 OOP:概述 面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承和动态绑定. 继承(inheritance): 通过继承联 ...

  2. Java第五章——面向对象(二)

    第五章面向对象(中) 1.访问控制修饰符 2.继承 3.组合 4.重载 5.覆盖(覆写) 重点: 封装(java访问控制权限) 继承(定义.子类访问控制权限) 类的组合的实现(与继承的区别) 多态(重 ...

  3. 第五章 函数和代码复用

    第五章 函数和代码复用 5.1 函数的基本使用 5.1.1 函数的定义 定义:函数是一段具有特定功能的.可重用的语句组,用函数名来表示并通过函数名进行功能调用. 使用函数的目的:降低编程难度和代码重用 ...

  4. 第五章 面向对象(上)

    Java是面向对象的程序设计语言,Java语言提供了定义类.成员变量.方法等最基础的功能,类可被认为是一种自定义的数据类型,可以使用类来定义变量,所有使用类定义的变量都是引用变量,它们将引用到类的对象 ...

  5. oracle 10g 新特性中文笔记(第五章)

    第五章 管理体系结构 目标 完成本课,你应该能: 描述自动工作负载资料库(AWR)的各种不同的组件 使用活动会话历史 定义AWR快照基线 对服务端的警告订阅应用程序 解释自动任务特征 描述顾问咨询框架 ...

  6. 第二十五章 面向对象------封装、内置函数、反射、动态导入

    1.封装 什么是封装? 1.对外部隐藏内部的属性,以及实现细节,给外部提供使用的接口 注意:封装有隐藏的意思,但不是单纯的隐藏 学习封装的目的:就是为了能够限制外界对内部数据的访问 python中属性 ...

  7. 第五章 ---- 面向对象(二)

    面向对象二 2.三大特征 2.1.继承性 2.1.1.方法重写 2.1.2.子类实例化过程 2.2.多态性 3.关键字 3.1.super 4.常用类说明 String String概述 字面量方式 ...

  8. 第五章面向对象编程设计与开发

    5.1 什么是面向对象的程序设计 面向过程的程序设计 优点:复杂的问题流程化,进而简单化 缺点:一套流水线或者流程就是用来解决一个问题,扩展性极差. 应用场景:面向过程的程序设计思想一般用于那些功能一 ...

  9. c++primer(第五版) 第十五章 面向对象程序设计习题答案

    纯原创    转载请注明出处:http://blog.csdn.net/axuan_k 略过书上有现成代码的题目 15.1  15.2 15.1 虚成员是基类中的概念 基类中的某些函数希望它的派生类能 ...

最新文章

  1. 透过源码详解Spring Security 初始化流程
  2. Python date,datetime,time等相关操作总结
  3. vue2.0 唤起百度地图app_如何标注百度地图?
  4. 7大最重要的管理方法
  5. Codeforces Round #661 (Div. 3)
  6. 从Memcache转战Redis,聊聊缓存使用填过的“坑”
  7. 《spring-boot学习》-09-spring boot+mybatis多数据源
  8. java merge_java – OptimisticLockException当使用JPA merge()
  9. C# Task 循环任务_聊聊 JavaScript 的并发、异步和事件循环
  10. 敏捷开发团队管理系列之五:大型研发团队的切分(刚参加3.17 MDP团队管理场次的读者请看)...
  11. zrender源码分析4--初始化Painter绘图模块2
  12. 利用Python使图片完美去除水印,我想试试马赛克的效果∧v∧
  13. 12306 抢票助手 Java
  14. 标识符——C语言基础知识
  15. 〖Python零基础入门篇④〗- Pycharm插件安装与常用的插件
  16. 【论文复现】CBAM(2018)
  17. css 文本移除及省略号位置
  18. 基于树莓派的智能家居控制系统设计论文参考
  19. 表格中insertBefore方法的使用(The node before which the new node is to be inserted is not a child of this no)
  20. 用pycharm进行python爬虫的步骤_使用Pycharm写一个网络爬虫

热门文章

  1. 上海出租车价格计算器
  2. python录音功能,python实现录音功能可随时停止录音代码
  3. overshoot是什么matlab,Overshoot metrics of bilevel waveform transitions
  4. ISP—图像调试实习生(第14天)
  5. OPCode详解及汇编与反汇编原理
  6. react开发vscode插件推荐
  7. SHA-512 逻辑
  8. PCM和G711A(PCMA)声音播放器和采集器
  9. ImageIO工具类简介及应用
  10. B站探索日历-推荐几个UP主