当前位置: 首页 > 工具软件 > pybind11 > 使用案例 >

pybind11学习 | 类的绑定

张溪叠
2023-12-01

本文主要记录官方文档中 CLASSES 一章的学习笔记。

对于C++ 类的Python绑定,在前面的学习中已经有所涉及了,详见:pybind11学习 | 面向对象编程。本文主要是记录一些更加深入的知识。在本文中只涉及了一些我感兴趣的部分,其他部分详见官方文档CLASSES 一章。

1 在Python中重载虚函数

一个含有虚函数的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_::defAnimal类的虚函数进行绑定是不行的,需要在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_OVERRIDEPYBIND11_OVERRIDE_PURE_NAMEPYBIND11_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! '

2 自定义构造函数

若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 &>());

3 隐式转换

假设有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>();

4 重载操作符

假设有这样一个类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 documentation

官方文档中文翻译:pybind11-Chinese-docs: pybind11中文文档

 类似资料: