7 个版本 (4 个重大更改)
0.5.0 | 2024年5月9日 |
---|---|
0.4.0 | 2024年3月8日 |
0.3.1 | 2024年3月5日 |
0.2.0 | 2024年1月23日 |
0.1.1 | 2024年1月20日 |
#24 in #python-module
在 pyo3_bindgen 中使用
205KB
4K SLoC
pyo3_bindgen
通过 PyO3 自动生成 Rust FFI 与 Python 模块的绑定。Python 模块将被递归分析以生成与所有公共类、函数、属性和常量结构相同的 Rust 绑定。任何可用的文档字符串和类型注释也将保留在其 Rust 等价物中。
以下是一个生成的 Rust 函数签名及其预期用法的示例。当然,手动将生成的绑定部分包裹在更符合 Rust 习惯的 API 中可能在大多数情况下都是有益的。
|
|
此项目旨在简化现有 Python 代码库与 Rust 的集成或过渡。您作为开发者,通过生成的绑定,立即获得 Rust 类型系统和现代编译语言的其他众多好处。此外,crates.io 上的所有高质量 crate 也都可供您使用。
生成的 Rust 代码本身并不提供比使用 Python 代码任何性能优势。然而,如果您决定用纯 Rust 重新编写代码库中性能关键的部分,它可以用作进一步优化的起点。
【注意】
0.5
版本的pyo3_bindgen
适配了在pyo3
版本0.21
中引入的新智能指针pyo3::Bound<'py, T>
。如果您需要旧的 "GIL Refs" API,请使用0.4
版本的pyo3_bindgen
。
概述
工作区包含以下包
- pyo3_bindgen: 用于生成绑定的公共 API(在
build.rs
中或通过过程宏) - pyo3_bindgen_cli: 通过
pyo3_bindgen
可执行文件生成绑定的 CLI 工具 - pyo3_bindgen_engine: 生成绑定的底层引擎
- pyo3_bindgen_macros: 用于就地生成的过程宏
pyo3_bindgen
的功能
macros
[实验性]: 启用import_python!
宏,该宏来自pyo3_bindgen_macros
包numpy
[实验性]: 启用 Pythonnumpy::ndarray
和 Rustnumpy::PyArray
之间的类型映射
说明
选项 1:构建脚本
首先,将 pyo3_bindgen
添加到您的 Cargo.toml
清单中的 构建依赖项。要实际使用生成的绑定,您还需要将 pyo3
添加为常规依赖项(或使用重导出的 pyo3_bindgen::pyo3
模块)。
[build-dependencies]
pyo3_bindgen = { version = "0.5" }
[dependencies]
pyo3 = { version = "0.21", features = ["auto-initialize"] }
然后,在您的包根目录中创建一个 build.rs
脚本,生成所选 Python 模块的绑定。在本例中,同时为 "os","posixpath" 和 "sys" Python 模块生成绑定。在生成过程结束时,Rust 绑定将写入 ${OUT_DIR}/bindings.rs
。
【提示】使用此方法,您还可以通过传递给构造函数的
pyo3_bindgen::Config
来自定义生成过程,例如Codegen::new(Config::builder().include_private(true).build())
来自定义生成过程。
//! build.rs
use pyo3_bindgen::Codegen;
fn main() -> Result<(), Box<dyn std::error::Error>> {
Codegen::default()
.module_names(["os", "posixpath", "sys"])?
.build(format!("{}/bindings.rs", std::env::var("OUT_DIR")?))?;
Ok(())
}
之后,您可以通过宏 include!
在 crate 的任何地方包含生成的 Rust 代码,并像使用常规 Rust 模块一样使用生成的绑定。但是,绑定必须在 pyo3::Python::with_gil
闭包中使用,以确保 Python GIL 被持有。
//! src/main.rs
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
fn main() -> pyo3::PyResult<()> {
pyo3::Python::with_gil(|py| {
// Get the path to the Python executable via "sys" Python module
let python_exe_path = sys::executable(py)?;
// Get the current working directory via "os" Python module
let current_dir = os::getcwd(py)?;
// Get the relative path to the Python executable via "posixpath" Python module
let relpath_to_python_exe = posixpath::relpath(py, python_exe_path, current_dir)?;
println!("Relative path to Python executable: '{relpath_to_python_exe}'");
Ok(())
})
}
选项 2:过程宏(实验性)
作为构建脚本的替代方案,您可以使用过程宏就地生成绑定。首先,将 pyo3_bindgen_macros
添加为 常规依赖 到您的 Cargo.toml
清单中,并启用 macros
功能。
[dependencies]
pyo3_bindgen = { version = "0.5", features = ["macros"] }
随后,可以在 crate 的任何地方使用宏 import_python!
生成所选 Python 模块的 Rust 绑定。如下例所示,为 "math" Python 模块生成了 Rust 绑定,可以直接在同一作用域中使用。与之前的方法类似,生成的绑定必须在 pyo3::Python::with_gil
闭包中使用,以确保 Python GIL 被持有。
[!NOTE] 与使用构建脚本不同,这种方法不提供通过
pyo3_bindgen::Config
进行相同级别的自定义。此外,过程宏相当实验性,可能不会在所有情况下都工作。
use pyo3_bindgen::import_python;
import_python!("math");
// Which Pi do you prefer?
// a) 🐍 Pi from Python "math" module
// b) 🦀 Pi from Rust standard library
// c) 🥧 Pi from your favourite bakery
pyo3::Python::with_gil(|py| {
let python_pi = math::pi(py).unwrap();
let rust_pi = std::f64::consts::PI;
assert_eq!(python_pi, rust_pi);
})
选项 3:CLI 工具
为了快速开始和测试,您可以使用 pyo3_bindgen
可执行文件生成和检查所选 Python 模块的绑定。该可执行文件作为一个独立包提供,可以通过 cargo
安装。
cargo install --locked pyo3_bindgen_cli
之后,运行 pyo3_bindgen
可执行文件以生成所选 Python 模块的 Rust 绑定。默认情况下,生成的绑定打印到 STDOUT,但也可以通过 -o
选项写入文件(有关更多信息,请参阅 pyo3_bindgen --help
)。
pyo3_bindgen -m os sys numpy -o bindings.rs
状态
该项目处于早期开发阶段,因此生成的绑定 API 还未稳定。
- 绑定生成主要设计用于在构建脚本或通过过程宏中使用。因此,对代码生成过程的性能进行了基准测试,以了解其对构建时间可能产生的影响。以下是针对版本
0.3
和默认配置的一些初步结果(未测量:生成的绑定的编译,这需要更长的时间)sys
: 1.24 毫秒(0.66k 总 LoC)os
: 8.38 毫秒(3.88k 总 LoC)numpy
: 1.02 秒(294k 总 LoC)torch
: 7.05 秒(1.08M 总 LoC)
- 只要目标 Python 模块可以成功导入,绑定生成永远不会崩溃。如果确实发生了这种情况,请 报告 作为错误。
- 生成的绑定应该始终是可编译和可用的 Rust。如果您遇到任何问题,请考虑手动修复绑定的问题部分,并请报告此问题作为错误。
- 然而,生成的绑定是基于目标 Python 模块的反射。因此,生成的绑定的完整性和正确性直接取决于目标 Python 模块的结构、类型注释和文档字符串的质量。理想情况下,生成的绑定应被视为不安全的,并作为安全且惯用的 Rust API 的起点。如果您发现生成的绑定中存在不正确或缺失的内容,请报告此问题。
- 并非所有 Python 类型都已映射到其 Rust 等价类型。因此,在使用生成的绑定时可能需要进行一些额外的类型转换(例如:
let typed_value: MyType = any_value.extract()?;
)。 - 虽然实现了过程宏,但它在许多情况下可能不起作用。因此,建议尽可能使用构建脚本。
许可证
本项目双许可,以与 Rust 项目兼容,可使用MIT或Apache 2.0许可证。
贡献
除非您明确声明,否则任何有意提交以包含在您的工作中的贡献(根据 Apache-2.0 许可证定义),均应双许可,如上所述,不附加任何额外条款或条件。
依赖项
~4–10MB
~96K SLoC