pybind11中在Python重写C++虚函数
1. 概述
pybind11提供了在Python中重写C++虚函数的强大功能,这使得我们可以:
-
在Python中继承C++类
-
重写C++类的虚函数
-
通过C++代码调用Python中重写的函数
-
实现多态行为
2. 基本原理
当C++类有虚函数时,正常的绑定方式无法支持在Python中扩展这些类。需要使用"trampoline"(跳板)类来将虚函数调用重定向回Python。
2.1 C++示例类
考虑以下C++类定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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); }
|
2.2 传统绑定方式的局限
传统的绑定代码如下,但无法支持扩展:
1 2 3 4 5 6 7 8 9
| PYBIND11_MODULE(example, m) { py::class_<Animal>(m, "Animal") .def("go", &Animal::go);
py::class_<Dog, Animal>(m, "Dog") .def(py::init<>());
m.def("call_go", &call_go); }
|
3. Trampoline类实现
3.1 Trampoline类定义
需要定义一个trampoline类作为中间人:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class PyAnimal : public Animal, public py::trampoline_self_life_support { public: using Animal::Animal;
std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE( std::string, Animal, go, n_times ); } };
|
3.2 Trampoline类的关键要素
-
继承关系:同时继承自原始C++类和py::trampoline_self_life_support
-
构造函数:使用using Animal::Animal继承父类构造函数
-
虚函数重写:每个虚函数都需要对应的trampoline实现
-
PYBIND11_OVERRIDE宏:用于将调用转发到Python
3.3 Trampoline宏详解
pybind11提供了几个宏来处理不同类型的虚函数:
-
PYBIND11_OVERRIDE_PURE:用于纯虚函数(没有默认实现)
-
PYBIND11_OVERRIDE:用于有默认实现的虚函数
-
PYBIND11_OVERRIDE_PURE_NAME:当C++和Python函数名不同时使用
-
PYBIND11_OVERRIDE_NAME:当C++和Python函数名不同时使用
4. 绑定代码修改
4.1 正确的绑定方式
1 2 3 4 5 6 7 8 9 10
| PYBIND11_MODULE(example, m) { py::class_<Animal, PyAnimal , py::smart_holder>(m, "Animal") .def(py::init<>()) .def("go", &Animal::go);
py::class_<Dog, Animal, py::smart_holder>(m, "Dog") .def(py::init<>());
m.def("call_go", &call_go); }
|
4.2 关键修改点
-
模板参数:将trampoline类作为第二个模板参数
-
智能指针:使用py::smart_holder作为持有者类型
-
绑定目标:绑定到原始C++类,而不是trampoline类
5. Python中使用
5.1 继承和重写虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| from example import *
d = Dog() call_go(d)
class Cat(Animal): def go(self, n_times): return "meow! " * n_times
c = Cat() call_go(c)
|
5.2 自定义构造函数
在Python中定义构造函数时,必须显式调用C++构造函数:
1 2 3 4 5 6 7
| class Dachshund(Dog): def __init__(self, name): Dog.__init__(self) self.name = name def bark(self): return f"{self.name} says: wuff!"
|
重要提示:从pybind11 2.6版本开始,如果派生类没有调用__init__,会抛出TypeError。
6. 高级特性和注意事项
6.1 返回引用或指针的限制
当重写的函数返回引用或指针时,需要注意:
6.2 智能指针和生命周期管理
std::shared_ptr vs py::smart_holder
| 特性 |
std::shared_ptr |
py::smart_holder |
| 继承切片 |
可能发生 |
避免 |
| weak_ptr行为 |
正常 |
可能有意外行为 |
| 推荐使用 |
兼容性优先 |
新代码推荐 |
6.3 模块本地类绑定
当多个模块绑定同一个C++类时,可能发生冲突。可以使用py::module_local():
1 2 3
| py::class_<Pet>(m, "Pet", py::module_local()) .def(py::init<std::string &>()) .def("name", &Pet::name);
|
这使得不同模块中的Pet类成为不同的Python类型。
6.4 访问保护成员
要在Python中访问C++类的保护成员,可以使用publicist模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Base { protected: void protected_method() { ... } };
struct BasePublicist : Base { using Base::protected_method; };
PYBIND11_MODULE(example, m) { py::class_<Base>(m, "Base") .def("protected_method", &BasePublicist::protected_method); }
|
6.5 最终类
使用py::is_final可以防止Python类继承:
1 2
| py::class_<FinalClass>(m, "FinalClass", py::is_final()) .def(py::init<>());
|
7. 性能考虑
-
Trampoline类在不需要时不会初始化,以提高性能
-
只有当Python类实际继承自注册类型时才会初始化
-
使用py::smart_holder通常比std::shared_ptr有更好的性能
8. 完整示例代码
8.1 C++绑定代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <pybind11/pybind11.h> #include <string>
namespace py = pybind11;
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); }
class PyAnimal : public Animal, public py::trampoline_self_life_support { public: using Animal::Animal; std::string go(int n_times) override { PYBIND11_OVERRIDE_PURE( std::string, Animal, go, n_times ); } };
PYBIND11_MODULE(example, m) { py::class_<Animal, PyAnimal, py::smart_holder>(m, "Animal") .def(py::init<>()) .def("go", &Animal::go);
py::class_<Dog, Animal, py::smart_holder>(m, "Dog") .def(py::init<>());
m.def("call_go", &call_go); }
|