PyO3 协议定制指南:用20+个魔术方法让Rust类成为原生Python对象
【免费下载链接】pyo3Rust bindings for the Python interpreter项目地址: https://gitcode.com/gh_mirrors/py/pyo3
PyO3是 Rust 调用 Python 解释器的官方绑定库。通过它,你可以在#[pymethods]中实现__init__、__str__、__len__、__add__等20+ 个魔术方法(dunder methods),让你的 Rust 结构体在 Python 眼里与list、dict、int一样"原生"——支持print、len()、比较运算、下标访问甚至直接当函数调用。
为什么需要定制协议?
Python 的"魔法"全部来自数据模型:当你写len(x)时,解释器实际调用的是x.__len__();print(x)调用x.__str__();x + y调用x.__add__(y)。
一个刚导出到 Python 的 Rust 类,默认只能被创建和调用普通方法,打印出来是生硬的<builtins.Number object at 0x7f...>。而 PyO3 的设计目标是:每个 Python 魔术方法都能在#[pymethods]中"照写照用",PyO3 会自动把它挂到 C 层的正确类型槽(slot)上,性能与纯 C 扩展一致。
📖 完整的协议清单见官方指南:guide/src/class/protocols.md
一分钟上手:构造器与普通方法的写法差异
先记住两条特殊规则:
| Python 写法 | PyO3 写法 |
|---|---|
def __init__(...) | 用#[new]属性标记的fn new(...) |
def __str__(...)等其他魔术方法 | 函数名直接写__str__,放在#[pymethods]块里 |
#[pyclass] struct Number(i32); #[pymethods] impl Number { #[new] // 替代 __init__ fn new(value: i32) -> Self { Self(value) } fn __str__(&self) -> String { self.0.to_string() } }这就是最小可用形态:Python 里str(Number(5))将输出"5"。
20+ 魔术方法速查表(按协议分类)
📌 这是全文最实用的一节,建议收藏。PyO3 自动处理的主要协议如下:
| 协议类别 | 关键魔术方法 | Python 中生效的语法 |
|---|---|---|
| 🏷️ 基础对象 | __str____repr____hash____bool____call____getattr__ | str(x)hash(x)bool(x)x() |
| ⚖️ 比较 | __lt____le____eq____ne____gt____ge____richcmp__ | <<===!=>>= |
| 🔁 迭代 | __iter____next____await____aiter____anext__ | for x in itasync for |
| 📦 容器 | __len____getitem____setitem____delitem____contains____concat____repeat__ | len(x)x[0]x[0]=11 in xx * 3 |
| 🔢 数值 | __add____sub____mul____truediv____mod____pow__及r*/i*变体、__neg____abs____int____float__ | +-*/%**int(x)float(x) |
| 🧹 垃圾回收 | __traverse____clear__ | 由 Python GC 自动触发 |
完整签名与返回值约束在 guide/src/class/protocols.md 中逐一列出。
手把手:5 步定制你的第一个"原生"Rust 类
第 1 步:字符串表示 ——__repr__与__str__
__repr__:面向开发者,理想状态下应能"复现"该对象,如Number(5);__str__:面向用户的友好输出,如5。
💡偷懒技巧:如果 Rust 类型实现了Display,只需一行注解即可自动生成__str__:
#[pyclass(str)] // 自动用 Display 生成 __str__ struct Coordinate { x: i32, y: i32, z: i32 }对结构体还有更短的写法#[pyclass(str = "({x}, {y}, {z})")],直接写格式串,见 guide/src/class/object.md。
第 2 步:比较运算 —— 一个__richcmp__搞定 6 个操作符
逐个实现__lt__、__gt__太啰嗦,PyO3 支持用__richcmp__一次覆盖全部比较:
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool { op.matches(self.0.cmp(&other.0)) // 用 Rust 的 Ord 一行搞定 }⚠️两个必知陷阱(详见 guide/src/class/object.md):
- 实现任意比较方法后,Python 会不再自动生成默认
__hash__,你的类将不可哈希——请同时实现__hash__; __richcmp__不能与__lt__等 6 个细粒度方法混用。
💡 更懒的技巧:#[pyclass(frozen, eq, hash)]+ Rust 的derive(PartialEq, Hash)可自动生成__eq__与__hash__。
第 3 步:让len()和in工作 —— 容器协议
fn __len__(&self) -> usize { self.vec.len() } fn __contains__(&self, item: &Bound<'_, PyAny>) -> PyResult<bool> { /* ... */ } fn __getitem__(&self, key: &Bound<'_, PyAny>) -> PyResult<PyObject> { /* ... */ }🎯进阶细节(这是新手最容易踩的坑):
- PyO3 默认会同时填充 mapping 槽和 sequence 槽。
dict这类映射型类建议加#[pyclass(mapping)],否则会意外获得基于下标的默认__iter__; - 想让
numpy等库把你的类识别为序列,用#[pyclass(sequence)],它还会自动处理负数下标。
真实项目中支持整数下标 + 切片的完整实现,可参考示例 examples/getitem/src/lib.rs。
第 4 步:可迭代 ——__iter__与__next__
让类支持for x in obj,只需两个方法:__iter__返回迭代器,__next__返回Option<值>——返回None即表示迭代结束(等价于 Python 抛StopIteration)。
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<usize> { slf.inner.next() }第 5 步:可调用 —— 让实例像函数一样使用
实现__call__后,实例可以直接obj()调用,参数表与普通方法完全相同。这是实现 Python 装饰器、回调计数器的经典手法,完整案例见官方示例 examples/decorator/src/lib.rs 与指南 guide/src/class/call.md。
进阶:缓冲区协议与垃圾回收集成
- Buffer 协议:实现
__getbuffer__/__releasebuffer__后,你的类可以被numpy、memoryview直接读取内存,是高性能数值库的标配; - GC 集成:当你的 Rust 类持有其他 Python 对象引用时,实现
__traverse__(用visit.call()报告每个引用)和__clear__(断开可变引用)参与 Python 循环垃圾回收。继承场景下父类的这两个方法会被自动调用,无需手动转发。
这两个方法对应 C API 的tp_traverse/tp_clear槽位,细节见 guide/src/class/protocols.md 的"Garbage Collector Integration"一节。
避坑清单 ⚡
| 常见疑问 | 正确答案 |
|---|---|
能不能写__init__? | 不能,PyO3 用#[new]构造器替代 |
能不能写__del__? | 目前不支持,析构逻辑写在 Rust 的Drop中 |
| 比较/算术方法参数类型不匹配会怎样? | 自动生成NotImplemented,Python 会尝试反射操作,而非报错 |
__hash__返回什么? | 任意 ≤64 位整数类型,PyO3 自动转换为isize |
学习路线与延伸阅读
- 📘 类与构造器基础:guide/src/class.md
- 📘 基础定制(字符串、哈希、比较):guide/src/class/object.md
- 📘 数值协议(溢出、包装、
wrapping策略):guide/src/class/call.md 与 guide/src/class/numeric.md - 📘 协议全量清单(本文速查表的出处):guide/src/class/protocols.md
- 🧪 可运行的下标访问示例:examples/getitem/
💬 一句话总结:Rust 负责性能与内存安全,Python 协议负责"手感"。用
#[pymethods]里的 20+ 个魔术方法把两者接起来,你的类型就能无缝融入 Python 生态——len()、+、for、in,一切如你所料。
【免费下载链接】pyo3Rust bindings for the Python interpreter项目地址: https://gitcode.com/gh_mirrors/py/pyo3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考