PyO3
Overview
PyO3 是一个用于 rust 和 python 代码互操作的库,可以用来给 python 代码写加速库,提升Python的运行速度。
1 | use pyo3::prelude::*; |
然后在Cargo.toml中添加以下内容:
1 | [lib] |
然后将整个rust crate编译成动态链接库,就可以在Python 代码中引入。在 windows 平台上整个模块会编译成 DLL 文件,我们需要手动将文件后缀名改为 .pyd; 而在 Mac 平台和 Linux 平台上,这个crate会编译成 .so文件,我们可以在python中直接引入这个文件。
PyO3官方给我们提供了手脚架 maturin,这个手脚架能帮我们自动处理跨平台的兼容性。我们可以简单使用命令pip install maturin安装这个手脚架。然后使用 maturin init在当前目录下初始化一个新PyO3的项目。(如果安装了uv的话则可以跳过安装步骤,直接使用uvx maturin init)
在开发的时候,我们可以将使用maturin develop直接将包安装到当前目录下的Python虚拟环境中,用于实时修改和 debug。
1 | your_project/ |
当我们编写完整个库后,就可以使用maturin build --release命令构建出.whl文件,最后在Python项目中使用pip安装编译出的.whl文件即可。
#[pymodule]
<font style="color:rgb(0, 0, 0);">#[pymodule]</font> 过程宏负责将模块的初始化函数导出到 Python,模块名称默认使用 Rust 函数的名称。你可以通过 <font style="color:rgb(0, 0, 0);">#[pyo3(name = "custom_name")]</font> 来覆盖模块名称,模块名称必须与 .so 或 .pyd 文件的名称匹配。否则在 Python 中导入时会报错。模块初始化函数的 Rust 文档注释将自动作为 Python 模块的文档字符串应用。
或者可以使用以下方法声明一个Python模块以及其成员/嵌套模块:
1 | use pyo3::prelude::*; |
#[pyfunction]
<font style="color:rgb(0, 0, 0);">#[pyfunction]</font> 属性用于从 Rust 函数定义 Python 函数。定义完成后,需要使用 <font style="color:rgb(0, 0, 0);">wrap_pyfunction! </font>宏将该函数添加到模块中。<font style="color:rgb(0, 0, 0);">#[pyo3] </font>属性可用于修改生成的 Python 函数的属性。它可以接受以下任意组合的选项:
<font style="color:rgb(0, 0, 0);">#[pyo3(name = "...")]</font>覆盖暴露给 Python 的名称。<font style="color:rgb(0, 0, 0);">#[pyo3(signature = "...")] </font>定义函数签名。<font style="color:rgb(0, 0, 0);">#[pyo3(text_signature = "...")]</font>设置函数签名在 Python 工具中的可见性(例如通过<font style="color:rgb(0, 0, 0);">help()</font>显示)。<font style="color:rgb(0, 0, 0);">#[pyo3(pass_module)]</font>设置此选项可使 PyO3 将包含模块作为第一个参数传递给函数,这样便可在函数体内使用该模块。第一个参数必须是<font style="color:rgb(0, 0, 0);">&Bound<'_, PyModule></font>、<font style="color:rgb(0, 0, 0);">Bound<'_, PyModule></font>或<font style="color:rgb(0, 0, 0);">Py<PyModule></font>类型。
#[pyo3(signature = (…))]
我们可以同过#[pyo3(signature = (**kwds))]用python的方式定义函数的参数类型。可以是以下结构:
/:仅限位置参数分隔符,每个在 / 之前定义的参数都是仅限位置参数*: 可变参数分隔符, * 之后定义的每个参数都是仅限关键字参数*args: “args” 表示可变参数。 args 参数的类型必须是&Bound<'_, PyTuple>**kwargs: “kwargs” 接收关键字参数。 kwargs 参数的类型必须是Option<&Bound<'_, PyDict>>arg=Value: 带默认值的参数。如果 arg 参数定义在可变参数之后,则被视为仅限关键字参数。注意 Value 必须是有效的 Rust 代码,PyO3 会直接将其原样插入生成的代码中
例如:
1 | use pyo3::types::{PyDict, PyTuple}; |
#[pyo3(from_py_with = “…”)]
#[pyo3(from_py_with = "...")] 属性可用于单个参数,以修改生成函数中这些参数的属性。例如:
1 | use pyo3::prelude::*; |
PyResult
- 任何可能返回Python 错误的的API函数都应该返回
PyResult<T>,PyResult<T>是类型Result<T, PyErr>的别名,PyErr则表示所有可能的Python错误。 - 所有内置 Python 异常类型都定义在 pyo3::exceptions 模块中。它们具有 new_err 构造函数,可直接构建 PyErr ,例如:
1 | use pyo3::exceptions::PyValueError; |
- 所有实现的
std::from::From<E> for PyErr的结构体都可以作为自定义的PyErr错误返回。
#[pyclass]
主要属性是 #[pyclass] ,它被放置在 Rust 的 struct 或 enum 上,为其生成 Python 类型。这些结构体通常还会有一个用 #[pymethods] 注解的 impl 代码块,用于为生成的 Python 类型定义方法和常量。(如果启用了 multiple-pymethods 功能,每个 #[pyclass] 可以拥有多个 #[pymethods] 代码块。) #[pymethods] 还可以实现 Python 魔术方法,例如 str 。
为了将 Rust 类型与 Python 集成,PyO3 需要对能用 #[pyclass] 注解的类型施加一些限制。具体来说,这些类型不能有生命周期参数、不能有泛型参数,且必须是线程安全的。下文将逐一解释这些限制的原因。
:::info
为什么不能有生命周期?
一旦 Rust 数据暴露给 Python,就无法保证 Rust 编译器能确定这些数据的存活时间。Python 是引用计数语言,这些引用可能被持有任意长的时间,而 Rust 编译器无法追踪。唯一正确的表达方式是要求任何 #[pyclass] 不能借用生命周期短于 'static 的数据,也就是说 #[pyclass] 不能有任何生命周期参数。
当需要在 Python 和 Rust 之间共享数据所有权时,不要使用带生命周期的借用引用,而应考虑使用引用计数的智能指针,如 Arc 或 Py 。
为什么需要是线程安全的?
Python 解释器可以自由在线程间共享 Python 对象。这意味着:
- Python 对象可能由不同的 Python 线程创建和销毁;因此 #[pyclass] 对象必须是 Send 的。
- Python 对象可能被多个 Python 线程同时访问;因此 #[pyclass] 对象必须是 Sync 的。
:::
#[pyclass] 可与以下参数配合使用:
| Parameter 参数 | Description 描述 |
|---|---|
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">constructor</font> |
目前仅允许在复杂枚举的变体上使用。它允许为每个变体自定义生成的类构造函数,使用与函数和方法 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">signature</font>属性相同的语法并支持相同的选项。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">crate = "some::path"</font> |
如果 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">pyo3</font>crate 无法通过 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">::pyo3</font>访问,则指定其导入路径。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">dict</font> |
为此类的实例提供一个空的 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__dict__</font>用于存储自定义属性。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">eq</font> |
使用底层 Rust 数据类型的 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">PartialEq</font> 实现来实现 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__eq__</font>。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">eq_int</font> |
对于简单枚举类型,使用 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__int__</font>实现 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__eq__</font>。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">extends = BaseType</font> |
使用自定义基类。默认为 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">PyAny</font> |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">freelist = N</font> |
实现大小为 N 的自由列表。对于频繁创建和删除的类型,这可以提高性能。通过性能分析判断 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">freelist</font>是否适合您的场景。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">frozen</font> |
声明您的 pyclass 是不可变的。这消除了获取 Rust 结构体共享引用时的借用检查器开销,但禁用了获取可变引用的能力。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">generic</font> |
为类实现遵循 PEP 560 的运行时参数化功能。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">get_all</font> |
为 pyclass 的所有字段生成 getter 方法。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">hash</font> |
使用底层 Rust 数据类型的 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">Hash</font>实现来实现 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__hash__</font>功能。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">immutable_type</font> |
使类型对象不可变。在 3.14+版本中需要启用 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">abi3</font>特性才支持,否则需 3.10+版本。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">mapping</font> |
告知 PyO3 这个类是一个 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">Mapping</font>,因此将其序列 C-API 槽位的实现留空。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">module = "module_name"</font> |
Python 代码会认为该类是在此模块中定义的。默认为 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">builtins</font> |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">name = "python_name"</font> |
设置 Python 中看到的类名称。默认为 Rust 结构体的名称。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">ord</font> |
使用底层 Rust 数据类型的 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">PartialOrd</font>实现来实现 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__lt__</font> 、 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__gt__</font> 、 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__le__</font>和 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__ge__</font> 。需要 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">eq</font> |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">rename_all = "renaming_rule"</font> |
将重命名规则应用于结构体的所有 getter 和 setter,或枚举的所有变体。可能的值为: + “camelCase”、 + “kebab-case”、 + “lowercase”、 + “PascalCase”、 + “SCREAMING-KEBAB-CASE”、 + “SCREAMING_SNAKE_CASE”、 + “snake_case”、 + “UPPERCASE”。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">sequence</font> |
通知 PyO3 这个类是一个 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">Sequence</font>,因此保持其 C-API 映射长度槽为空。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">set_all</font> |
为 pyclass 的所有字段生成 setter 方法。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">str</font> |
使用底层 Rust 数据类型的 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">Display</font>实现或通过传递可选的格式字符串 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">str="<format string>"</font>来实现 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">__str__</font>。注意:可选格式字符串仅适用于结构体。 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">name</font>和 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">rename_all</font>与可选格式字符串不兼容。更多细节可在此 PR 讨论中找到。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">subclass</font> |
允许其他 Python 类和 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">#[pyclass]</font>继承此类。枚举类型不可被子类化。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">unsendable</font> |
当您的结构体不是 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">Send</font>时必须使用。与其使用 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">unsendable</font>,不如通过例如用 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">Arc</font>替换 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">Rc</font>的方式实现线程安全的结构体。使用 <font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">unsendable</font>时,当其他线程访问您的类会导致 panic。另请注意 Python 的 GC 是多线程的,虽然不可发送的类不会在外部线程上遍历以避免未定义行为,但这可能导致内存泄漏。 |
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">weakref</font> |
允许此类被弱引用。 |
#[new]
要声明构造函数,需要定义一个方法并用 <font style="color:rgb(0, 0, 0);">#[new]</font> 属性进行标注。若未声明任何标记为 #[new] 的方法,对象实例只能从 Rust 创建,而无法通过 Python 创建。
对于可能失败的构造函数,你也应该将返回类型包装在 PyResult 中。
#[pyo3(get, set)]
- 对于没有副作用的简单结构体字段,可以直接在 #[pyclass] 的字段定义上添加 #[pyo3(get, set)] 属性。若要以不同于字段的名称公开该属性,可在其他选项中指定,例如
#[pyo3(get, set, name = "custom_name")]。- 对于
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">get</font>,字段类型必须同时实现<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">IntoPy<PyObject></font>和<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">Clone</font> - 对于
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">set</font>,字段类型必须实现<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">FromPyObject</font>
- 对于
- 对于需要计算的属性,您可以在 #[pymethods] 代码块中定义 #[getter] 和 #[setter] 函数。
<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">#[getter]</font>和<font style="color:rgb(48, 25, 0);background-color:rgb(246, 247, 246);">#[setter]</font>属性都接受一个参数。若指定该参数,则将其用作属性名。
#[classmethod]
类似于python的@classmethod
#[staticmethod]
类似于python的@staticmathod
注册自定义类
1 |
|