本文主要记录官方文档中 CLASSES 一章的学习笔记。
对于C++ 类的Python绑定,在前面的学习中已经有所涉及了,详见:pybind11学习 | 面向对象编程。本文主要是记录一些更加深入的知识。在本文中只涉及了一些我感兴趣的部分,其他部分详见官方文档CLASSES 一章。
一个含有虚函数的C++类。
class Animal {
public:
virtual ~Animal() { }
virtual std::string go(int n_times) = 0;
};
class Dog : public Animal {
public:
std::string go(int n_times) override {
std::string result;
for (int i=0; i<n_times; ++i)
result += "woof! ";
return result;
}
};
std::string call_go(Animal *animal) {
return animal->go(3);
}
我们在Python中自定义一个继承自Animal
的新类,并想要重载基类中的虚函数。此时,直接class_::def
对Animal
类的虚函数进行绑定是不行的,需要在C++中再定义一个新的PyAnimal类(继承自Animal类)作为辅助跳板。
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) */
);
}
};
上面代码中,定义纯虚函数时需要使用PYBIND11_OVERRIDE_PURE
宏,而有默认实现的虚函数则使用PYBIND11_OVERRIDE
。PYBIND11_OVERRIDE_PURE_NAME
和PYBIND11_OVERRIDE_NAME
宏的功能类似,主要用于C函数名和Python函数名不一致的时候。pybind11绑定代码如下:
PYBIND11_MODULE(example, m) {
py::class_<Animal, PyAnimal /* <--- 跳板类*/>(m, "Animal")
.def(py::init<>())
.def("go", &Animal::go); // 绑定的是真实类的方法,而不是跳板类的方法
py::class_<Dog, Animal>(m, "Dog")
.def(py::init<>());
m.def("call_go", &call_go);
}
pybind11通过向class_
指定额外的模板参数PyAnimal,让我们可以在Python中继承Animal类(即重载C++类中的为虚函数的构造函数)。Python中测试如下:
from example import *
d = Dog()
call_go(d) # 'woof! woof! woof! '
class Cat(Animal): # 继承
def go(self, n_times): # 重载虚函数
return "meow! " * n_times
c = Cat()
call_go(c) # 'meow! meow! meow! '
若C++类中没有构造函数,我们可以显式地将自定义函数作为类的构造函数绑定到Python类的__init__
方法上。pybind11通过调用.def(py::init(...))
,将对应的函数(函数需要返回一个新实例)作为参数传入py::init()
实现。py::init()
也可以传入返回新实例原始指针或持有者的匿名函数。
class Example {
private:
Example(int); // private constructor
public:
// Factory function - returned by value:
static Example create(int a) { return Example(a); }
// These constructors are publicly callable:
Example(double);
Example(int, int);
Example(std::string);
};
py::class_<Example>(m, "Example")
// 绑定一个工厂函数作为构造函数
.def(py::init(&Example::create))
// 绑定匿名函数返回实例的原始指针的持有者
.def(py::init([](std::string arg) {
return std::unique_ptr<Example>(new Example(arg));
}))
// 匿名函数返回原始指针
.def(py::init([](int a, int b) { return new Example(a, b); }))
// 对构造函数进行常规绑定
.def(py::init<double>())
;
pybind11
使用C++11的大括号初始化来隐式调用目标类的构造函数。
struct Aggregate {
int a;
std::string b;
};
py::class_<Aggregate>(m, "Aggregate")
.def(py::init<int, const std::string &>());
假设有A和B两个类,A可以直接转换为B。
py::class_<A>(m, "A")
/// ... members ...
py::class_<B>(m, "B")
.def(py::init<A>())
/// ... members ...
m.def("func",
[](const B &) { /* .... */ }
);
如果想func函数传入A类型的参数a,Pyhton侧需要这样写func(B(a))
,而C++侧则可以直接使用func(a)
,自动将A类型转换为B类型。
这种情形下(B有一个接受A类型参数的构造函数),我们可以使用如下声明来让Python侧也支持类似的隐式转换:
py::implicitly_convertible<A, B>();
假设有这样一个类Vector2
,它通过重载操作符实现了向量加法和标量乘法。
class Vector2 {
public:
Vector2(float x, float y) : x(x), y(y) { }
Vector2 operator+(const Vector2 &v) const { return Vector2(x + v.x, y + v.y); }
Vector2 operator*(float value) const { return Vector2(x * value, y * value); }
Vector2& operator+=(const Vector2 &v) { x += v.x; y += v.y; return *this; }
Vector2& operator*=(float v) { x *= v; y *= v; return *this; }
friend Vector2 operator*(float f, const Vector2 &v) {
return Vector2(f * v.x, f * v.y);
}
std::string toString() const {
return "[" + std::to_string(x) + ", " + std::to_string(y) + "]";
}
private:
float x, y;
};
操作符绑定代码如下:
#include <pybind11/operators.h>
PYBIND11_MODULE(example, m) {
py::class_<Vector2>(m, "Vector2")
.def(py::init<float, float>())
.def(py::self + py::self)
.def(py::self += py::self)
.def(py::self *= float())
.def(float() * py::self)
.def(py::self * float())
// 上面一行等同于:
/*.def("__mul__", [](const Vector2 &a, float b) {
return a * b;
}, py::is_operator())*/
.def(-py::self)
.def("__repr__", &Vector2::toString);
}
官方文档中文翻译:pybind11-Chinese-docs: pybind11中文文档