第5章 类与对象绑定
类绑定是 pybind11 最强大的特性之一。通过 py::class_ 可以将 C++ 类完整地暴露给 Python,包括构造函数、成员函数、静态函数、属性、运算符重载和继承层次。
5.1 简单类绑定
Section titled “5.1 简单类绑定”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
// 定义一个简单的 C++ 类class Point {public: Point(int x, int y) : m_x(x), m_y(y) {} int x() const { return m_x; } int y() const { return m_y; } void set_x(int x) { m_x = x; } void set_y(int y) { m_y = y; }
private: int m_x; int m_y;};
PYBIND11_MODULE(class_basic, m) { // 使用 py::class_<T> 绑定类 // 语法: py::class_<ClassName>(m, "PythonName") py::class_<Point>(m, "Point") // .def(py::init<...>()) 绑定构造函数 .def(py::init<int, int>(), py::arg("x") = 0, py::arg("y") = 0, "构造函数:创建一个 Point 对象") // 绑定成员函数 .def("x", &Point::x, "获取 x 坐标") .def("y", &Point::y, "获取 y 坐标") .def("set_x", &Point::set_x, py::arg("x"), "设置 x 坐标") .def("set_y", &Point::set_y, py::arg("y"), "设置 y 坐标") // 绑定 __repr__ .def("__repr__", [](const Point& p) { return py::str("<Point({}, {})>").format(p.x(), p.y()); });}Python 使用
Section titled “Python 使用”import class_basic as cb
p = cb.Point(3, 4)print(p) # <Point(3, 4)>print(p.x()) # 3print(p.y()) # 4
p.set_x(10)print(p.x()) # 10py::class_<Point>(m, "Point") .def(py::init<int, int>(), ...)| 表达式 | 含义 |
|---|---|
py::class_<Point> | 模板参数是 C++ 类类型 |
(m, "Point") | 在模块 m 中注册为 "Point" |
py::init<int, int>() | 绑定构造函数,参数类型为 int, int |
&Point::x | 成员函数指针 |
5.2 构造函数重载
Section titled “5.2 构造函数重载”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>#include <pybind11/stl.h>
namespace py = pybind11;
class Rectangle {public: Rectangle(int width, int height) : m_w(width), m_h(height) {} Rectangle() : m_w(0), m_h(0) {}
int area() const { return m_w * m_h; } int perimeter() const { return 2 * (m_w + m_h); }
private: int m_w; int m_h;};
PYBIND11_MODULE(constructor_overload, m) { py::class_<Rectangle>(m, "Rectangle") // 默认构造函数 .def(py::init<>(), "创建零面积矩形") // 带参数的构造函数 .def(py::init<int, int>(), py::arg("width") = 1, py::arg("height") = 1, "创建指定宽高的矩形") .def("area", &Rectangle::area, "计算面积") .def("perimeter", &Rectangle::perimeter, "计算周长");}Python 使用
Section titled “Python 使用”import constructor_overload as co
r1 = co.Rectangle() # 默认构造r2 = co.Rectangle(3, 4) # 带参数构造r3 = co.Rectangle(width=5) # 部分参数
print(r2.area()) # 12print(r3.perimeter()) # 12 (5+5+1+1)5.3 成员函数绑定
Section titled “5.3 成员函数绑定”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class Circle {public: Circle(double radius) : m_radius(radius) {}
// getter double radius() const { return m_radius; }
// setter void set_radius(double r) { if (r < 0) throw std::runtime_error("Radius cannot be negative"); m_radius = r; }
// 计算方法 double area() const { return 3.14159 * m_radius * m_radius; } double circumference() const { return 2 * 3.14159 * m_radius; }
private: double m_radius;};
PYBIND11_MODULE(member_functions, m) { py::class_<Circle>(m, "Circle") .def(py::init<double>(), py::arg("radius") = 1.0, "创建圆") .def("radius", &Circle::radius, "获取半径") .def("set_radius", &Circle::set_radius, py::arg("radius"), "设置半径") .def("area", &Circle::area, "计算面积") .def("circumference", &Circle::circumference, "计算周长") .def("__repr__", [](const Circle& c) { return py::str("<Circle(radius={:.2f})>").format(c.radius()); });}Python 使用
Section titled “Python 使用”import member_functions as mf
c = mf.Circle(5.0)print(c.radius()) # 5.0print(c.area()) # 78.53975print(c.circumference()) # 31.4159
c.set_radius(3.0)print(c.area()) # 28.27431
try: c.set_radius(-1.0)except RuntimeError as e: print(f"Error: {e}") # Radius cannot be negative5.4 静态成员函数
Section titled “5.4 静态成员函数”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class MathUtils {public: // 静态成员函数:不需要实例即可调用 static int add(int a, int b) { return a + b; } static int multiply(int a, int b) { return a * b; }
// 静态成员变量 static constexpr double PI = 3.14159265358979;
// 非静态成员函数(普通方法) int power(int base, int exp) const { int result = 1; for (int i = 0; i < exp; ++i) result *= base; return result; }};
PYBIND11_MODULE(static_methods, m) { py::class_<MathUtils>(m, "MathUtils") .def(py::init<>(), "创建 MathUtils 实例") // 静态函数绑定使用 .def_static() .def_static("add", &MathUtils::add, "两数相加") .def_static("multiply", &MathUtils::multiply, "两数相乘") // 暴露静态常量 .attr("PI") = MathUtils::PI;
// 直接在模块级别暴露静态函数(不需要类) m.def("math_add", &MathUtils::add, "使用 MathUtils.add 的顶层函数");}Python 使用
Section titled “Python 使用”import static_methods as sm
print(sm.MathUtils.add(3, 4)) # 7print(sm.MathUtils.multiply(3, 4)) # 12
print(sm.MathUtils.PI) # 3.14159265358979
math = sm.MathUtils()print(math.power(2, 10)) # 1024.def vs .def_static
Section titled “.def vs .def_static”.class_<MyClass>(m, "MyClass") .def("instance_method", &MyClass::instance_method) // 实例方法 .def_static("static_method", &MyClass::static_method) // 静态方法5.5 属性绑定(Getter/Setter)
Section titled “5.5 属性绑定(Getter/Setter)”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class Person {public: Person(const std::string& name, int age) : m_name(name), m_age(age) {}
const std::string& name() const { return m_name; } void set_name(const std::string& n) { m_name = n; }
int age() const { return m_age; } void set_age(int a) { if (a < 0) throw std::runtime_error("Age cannot be negative"); m_age = a; }
private: std::string m_name; int m_age;};
PYBIND11_MODULE(properties, m) { py::class_<Person>(m, "Person") .def(py::init<std::string, int>(), py::arg("name"), py::arg("age") = 0, "创建 Person 对象") // 使用 .def_property() 绑定 getter/setter // .def_property(name, getter, setter, doc) .def_property("name", &Person::name, &Person::set_name, "姓名属性") .def_property("age", &Person::age, &Person::set_age, "年龄属性") // 只读属性:只提供 getter .def_property_readonly("info", [](const Person& p) { return py::str("{} (age {})").format(p.name(), p.age()); }, "只读信息字符串");}Python 使用
Section titled “Python 使用”import properties as p
person = p.Person("Alice", 30)
print(person.name) # Aliceprint(person.age) # 30
person.name = "Bob"person.age = 25print(person.name) # Bobprint(person.info) # Bob (age 25)| 方法 | 用途 |
|---|---|
.def_property() | 可读写属性,提供 getter 和 setter |
.def_property_readonly() | 只读属性,只有 getter |
.def_property_readwrite() | 可读写属性,与 .def_property() 相同 |
5.6 只读属性
Section titled “5.6 只读属性”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class Database {public: Database(const std::string& name) : m_name(name), m_id(next_id++) {}
const std::string& name() const { return m_name; } int id() const { return m_id; }
void set_name(const std::string& n) { m_name = n; }
private: std::string m_name; int m_id; static int next_id;};
int Database::next_id = 1;
PYBIND11_MODULE(readonly_property, m) { py::class_<Database>(m, "Database") .def(py::init<std::string>(), py::arg("name"), "创建数据库连接") // name 可读写 .def_property("name", &Database::name, &Database::set_name) // id 只读(没有 setter) .def_property_readonly("id", &Database::id, "数据库 ID(只读)");}Python 使用
Section titled “Python 使用”import readonly_property as rp
db = rp.Database("mydb")print(db.name) # mydbprint(db.id) # 1
db.name = "newdb"print(db.name) # newdb5.7 运算符重载
Section titled “5.7 运算符重载”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class Vector2 {public: Vector2(double x, double y) : m_x(x), m_y(y) {}
// 运算符重载 Vector2 operator+(const Vector2& other) const { return Vector2(m_x + other.m_x, m_y + other.m_y); }
Vector2 operator-(const Vector2& other) const { return Vector2(m_x - other.m_x, m_y - other.m_y); }
Vector2 operator*(double scalar) const { return Vector2(m_x * scalar, m_y * scalar); }
bool operator==(const Vector2& other) const { return std::abs(m_x - other.m_x) < 1e-9 && std::abs(m_y - other.m_y) < 1e-9; }
double x() const { return m_x; } double y() const { return m_y; }
private: double m_x; double m_y;};
PYBIND11_MODULE(operators, m) { py::class_<Vector2>(m, "Vector2") .def(py::init<double, double>(), py::arg("x") = 0, py::arg("y") = 0) .def("x", &Vector2::x) .def("y", &Vector2::y) // 数学运算 .def(py::self + py::self) // __add__ .def(py::self - py::self) // __sub__ .def(py::self * double()) // __mul__ (scalar * vector) // 比较运算 .def(py::self == py::self) // __eq__ // 支持反序运算(右操作数) .def(double() * py::self) // __rmul__ (vector * scalar) .def("__repr__", [](const Vector2& v) { return py::str("Vector2({:.2f}, {:.2f})").format(v.x(), v.y()); });}Python 使用
Section titled “Python 使用”import operators as op
v1 = op.Vector2(1.0, 2.0)v2 = op.Vector2(3.0, 4.0)
print(v1 + v2) # Vector2(4.00, 6.00)print(v1 - v2) # Vector2(-2.00, -2.00)print(v1 * 2.0) # Vector2(2.00, 4.00)print(3.0 * v1) # Vector2(3.00, 6.00) (__rmul__)
v3 = op.Vector2(1.0, 2.0)print(v1 == v3) # Trueprint(v1 == v2) # False常用运算符映射
Section titled “常用运算符映射”| C++ 运算符 | pybind11 表达式 | Python 魔术方法 |
|---|---|---|
a + b | py::self + py::self | __add__ |
a - b | py::self - py::self | __sub__ |
a * b | py::self * py::self | __mul__ |
a == b | py::self == py::self | __eq__ |
a[i] | py::self[i] | __getitem__ |
a() | py::self() | __call__ |
5.8 友元函数
Section titled “5.8 友元函数”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class Point;
class PointPrinter {public: // 友元函数 static void print(const Point& p);};
class Point {public: Point(int x, int y) : m_x(x), m_y(y) {}
int x() const { return m_x; } int y() const { return m_y; }
// 友元声明 friend class PointPrinter;
private: int m_x; int m_y;};
void PointPrinter::print(const Point& p) { // 可以访问 Point 的私有成员 py::print(py::str("Point({}, {})").format(p.m_x, p.m_y));}
PYBIND11_MODULE(friend_functions, m) { py::class_<Point>(m, "Point") .def(py::init<int, int>(), py::arg("x"), py::arg("y")) .def("x", &Point::x) .def("y", &Point::y);
// 绑定友元函数到模块 m.def("print_point", &PointPrinter::print, py::arg("point"), "打印 Point 对象");}Python 使用
Section titled “Python 使用”import friend_functions as ff
p = ff.Point(3, 4)ff.print_point(p) # 输出: Point(3, 4)5.9 虚函数与继承(基础)
Section titled “5.9 虚函数与继承(基础)”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
// 基类class Animal {public: virtual ~Animal() = default;
virtual std::string speak() const = 0; virtual std::string name() const { return m_name; }
protected: std::string m_name;};
// 派生类class Dog : public Animal {public: Dog(const std::string& name) { m_name = name; }
std::string speak() const override { return m_name + " says: Woof!"; }};
class Cat : public Animal {public: Cat(const std::string& name) { m_name = name; }
std::string speak() const override { return m_name + " says: Meow!"; }};
PYBIND11_MODULE(inheritance, m) { // 基类绑定(抽象类,不能直接实例化) py::class_<Animal>(m, "Animal") .def("name", &Animal::name) .def("speak", &Animal::speak, "动物发出的声音");
// 派生类绑定 py::class_<Dog, Animal>(m, "Dog") .def(py::init<const std::string&>(), py::arg("name")) .def("speak", &Dog::speak, "狗叫声");
py::class_<Cat, Animal>(m, "Cat") .def(py::init<const std::string&>(), py::arg("name")) .def("speak", &Cat::speak, "猫叫声");}Python 使用
Section titled “Python 使用”import inheritance as inh
dog = inh.Dog("Buddy")print(dog.name()) # Buddyprint(dog.speak()) # Buddy says: Woof!
cat = inh.Cat("Whiskers")print(cat.name()) # Whiskersprint(cat.speak()) # Whiskers says: Meow!
animals = [dog, cat]for a in animals: print(f"{a.name()}: {a.speak()}")5.10 虚函数覆写(Trampoline 模式)
Section titled “5.10 虚函数覆写(Trampoline 模式)”当 Python 代码需要覆写 C++ 的虚函数时,需要使用 Trampoline(蹦床)模式。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class PyAnimal; // 前向声明
// 基类class Animal {public: virtual ~Animal() = default;
// 纯虚函数 virtual std::string speak() const = 0;
// 非虚函数 virtual std::string name() const { return "Animal"; }
// 普通方法 std::string identify() const { return "I am a " + name(); }};
// Trampoline 类:继承自 C++ 基类,覆写虚函数以调用 Pythonclass PyAnimal : public Animal {public: // 覆写 speak std::string speak() const override { // 调用 Python 端的实现 PYBIND11_OVERRIDE_PURE( std::string, // 返回类型 Animal, // 基类类型 speak, // 函数名 ); }
// 覆写 name std::string name() const override { PYBIND11_OVERRIDE( std::string, Animal, name, ); }};
class Dog : public Animal {public: Dog(const std::string& name) : m_name(name) {}
std::string speak() const override { return m_name + " says: Woof!"; }
std::string name() const override { return m_name; }
private: std::string m_name;};
PYBIND11_MODULE(trampoline, m) { // 基类绑定时指定 trampoline 类 py::class_<Animal, PyAnimal>(m, "Animal") .def(py::init<>()) .def("name", &Animal::name) .def("speak", &Animal::speak) .def("identify", &Animal::identify);
py::class_<Dog, Animal>(m, "Dog") .def(py::init<const std::string&>(), py::arg("name"));}Python 使用
Section titled “Python 使用”import trampoline as tr
dog = tr.Dog("Rex")print(dog.speak()) # Rex says: Woof!print(dog.identify()) # I am a Rex
class MyAnimal(tr.Animal): def name(self): return "CustomAnimal"
def speak(self): return "Custom sound!"
my_animal = MyAnimal()print(my_animal.name()) # CustomAnimalprint(my_animal.speak()) # Custom soundprint(my_animal.identify()) # I am a CustomAnimalPYBIND11_OVERRIDE vs PYBIND11_OVERRIDE_PURE
Section titled “PYBIND11_OVERRIDE vs PYBIND11_OVERRIDE_PURE”| 宏 | 用途 | 场景 |
|---|---|---|
PYBIND11_OVERRIDE_PURE | 基类函数是纯虚(= 0) | 必须覆写 |
PYBIND11_OVERRIDE | 基类函数有默认实现 | 可选覆写 |
// 语法PYBIND11_OVERRIDE_PURE(ReturnType, BaseClass, func_name)PYBIND11_OVERRIDE(ReturnType, BaseClass, func_name, args...)5.11 抽象类与接口
Section titled “5.11 抽象类与接口”C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
// 抽象基类(接口)class IProcessor {public: virtual ~IProcessor() = default; virtual std::string process(const std::string& input) = 0; virtual int priority() const { return 0; }};
// 具体实现class UpperCaseProcessor : public IProcessor {public: std::string process(const std::string& input) override { std::string result = input; std::transform(result.begin(), result.end(), result.begin(), ::toupper); return result; }};
class LowerCaseProcessor : public IProcessor {public: std::string process(const std::string& input) override { std::string result = input; std::transform(result.begin(), result.end(), result.begin(), ::tolower); return result; }
int priority() const override { return 1; }};
// Trampolineclass PyProcessor : public IProcessor {public: std::string process(const std::string& input) override { PYBIND11_OVERRIDE_PURE( std::string, IProcessor, process, input ); }
int priority() const override { PYBIND11_OVERRIDE( int, IProcessor, priority, ); }};
PYBIND11_MODULE(abstract, m) { // 绑定接口(抽象类) py::class_<IProcessor, PyProcessor>(m, "IProcessor") .def(py::init<>()) .def("process", &IProcessor::process, py::arg("input"), "处理输入") .def("priority", &IProcessor::priority, "处理优先级");
// 绑定具体实现 py::class_<UpperCaseProcessor, IProcessor>(m, "UpperCaseProcessor") .def(py::init<>());
py::class_<LowerCaseProcessor, IProcessor>(m, "LowerCaseProcessor") .def(py::init<>());}Python 使用
Section titled “Python 使用”import abstract as ab
upper = ab.UpperCaseProcessor()print(upper.process("hello")) # HELLO
lower = ab.LowerCaseProcessor()print(lower.process("WORLD")) # world
class MyProcessor(ab.IProcessor): def process(self, input): return f"***{input}***"
def priority(self): return 99
my_proc = MyProcessor()print(my_proc.process("test")) # ***test***print(my_proc.priority()) # 995.12 私有成员处理
Section titled “5.12 私有成员处理”Python 不能直接访问 C++ 的 private 成员,但可以通过公共接口间接访问。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
class BankAccount {public: BankAccount(const std::string& name, double balance) : m_name(name), m_balance(balance) {}
// 公共接口 const std::string& name() const { return m_name; } double balance() const { return m_balance; }
// 存款(修改私有成员的唯一方式) void deposit(double amount) { if (amount <= 0) throw std::runtime_error("Amount must be positive"); m_balance += amount; }
// 取款 void withdraw(double amount) { if (amount <= 0) throw std::runtime_error("Amount must be positive"); if (amount > m_balance) throw std::runtime_error("Insufficient funds"); m_balance -= amount; }
// 私有成员不能直接绑定 // py::class_<BankAccount>(m, "BankAccount") // .def_readwrite("m_balance", ...) // 编译错误!
private: std::string m_name; // 私有成员 double m_balance; // 私有成员
// 私有方法 void audit() { /* 审计逻辑 */ }};
PYBIND11_MODULE(private_members, m) { py::class_<BankAccount>(m, "BankAccount") .def(py::init<std::string, double>(), py::arg("name"), py::arg("balance") = 0.0, "创建银行账户") .def("name", &BankAccount::name, "获取账户名") .def("balance", &BankAccount::balance, "获取余额") .def("deposit", &BankAccount::deposit, py::arg("amount"), "存款") .def("withdraw", &BankAccount::withdraw, py::arg("amount"), "取款") .def("__repr__", [](const BankAccount& acc) { return py::str("<BankAccount({}, {:.2f})>").format(acc.name(), acc.balance()); });}Python 使用
Section titled “Python 使用”import private_members as pm
account = pm.BankAccount("Alice", 1000.0)print(account) # <BankAccount(Alice, 1000.00)>
print(account.name()) # Aliceprint(account.balance()) # 1000.0
account.deposit(500)print(account.balance()) # 1500.0
account.withdraw(200)print(account.balance()) # 1300.0私有成员访问策略
Section titled “私有成员访问策略”| 方式 | 说明 |
|---|---|
| 公共 Getter/Setter | 标准 OOP 方式,推荐 |
def_readonly() | 绑定为只读属性 |
| 内部指针引用 | 使用 py::return_value_policy |
最佳实践: Python 不应该直接访问 C++ 的私有成员。通过设计良好的公共接口来控制访问,这样符合封装原则,也更安全。
本章涵盖了 pybind11 类绑定的核心概念:
| 主题 | 关键点 |
|---|---|
| 类绑定 | py::class_<T>(m, "Name") |
| 构造函数 | py::init<Args>(),支持重载 |
| 成员函数 | .def("name", &Class::method) |
| 静态函数 | .def_static("name", &Class::static_method) |
| 属性 | .def_property() / .def_property_readonly() |
| 运算符 | py::self + py::self 等表达式 |
| 继承 | py::class_<Derived, Base> |
| 虚函数覆写 | Trampoline 模式 + PYBIND11_OVERRIDE |
| 抽象类 | 纯虚函数 + trampoline |
| 私有成员 | 通过公共接口访问 |
下一章我们将学习如何导出 C++ 枚举和常量到 Python。