PyO3 协议定制指南:用20+个魔术方法让Rust类成为原生Python对象
2026/9/21 16:38:52 网站建设 项目流程

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 眼里与listdictint一样"原生"——支持printlen()、比较运算、下标访问甚至直接当函数调用。

为什么需要定制协议?

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):

  1. 实现任意比较方法后,Python 会不再自动生成默认__hash__,你的类将不可哈希——请同时实现__hash__
  2. __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__后,你的类可以被numpymemoryview直接读取内存,是高性能数值库的标配;
  • 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

学习路线与延伸阅读

  1. 📘 类与构造器基础:guide/src/class.md
  2. 📘 基础定制(字符串、哈希、比较):guide/src/class/object.md
  3. 📘 数值协议(溢出、包装、wrapping策略):guide/src/class/call.md 与 guide/src/class/numeric.md
  4. 📘 协议全量清单(本文速查表的出处):guide/src/class/protocols.md
  5. 🧪 可运行的下标访问示例:examples/getitem/

💬 一句话总结:Rust 负责性能与内存安全,Python 协议负责"手感"。用#[pymethods]里的 20+ 个魔术方法把两者接起来,你的类型就能无缝融入 Python 生态——len()print+forin,一切如你所料。

【免费下载链接】pyo3Rust bindings for the Python interpreter项目地址: https://gitcode.com/gh_mirrors/py/pyo3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询