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日 |
#87 in FFI
38KB
66 行
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
[实验性]: 启用来自pyo3_bindgen_macros
crate 的import_python!
宏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"] }
然后,在您的 crate 根目录中创建一个 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!
宏在任何地方包含生成的 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 被持有。
[!注意] 与使用构建脚本不同,此方法不提供通过
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 ms(0.66k 总代码行数)os
: 8.38 ms(3.88k 总代码行数)numpy
: 1.02 s(294k 总代码行数)torch
: 7.05 s(1.08M 总代码行数)
- 只要目标 Python 模块可以成功导入,绑定生成就不会 panic。如果发生 panic,请 报告 这作为 bug。
- 生成的绑定应该始终是可编译的,并且可以在 Rust 中使用。如果您遇到任何问题,请手动修复绑定中的问题,并将此问题报告为错误。
- 然而,生成的绑定是基于目标 Python 模块的反射生成的。因此,生成的绑定的完整性和正确性直接取决于目标 Python 模块的结构、类型注解和文档字符串的质量。理想情况下,生成的绑定应被视为不安全的,并作为创建安全且符合 Rust 风格的 API 的起点。如果您发现生成的绑定中有不正确或不完整的地方,请报告此问题。
- 并非所有 Python 类型都已映射到它们的 Rust 等价类型。因此,在使用生成的绑定时可能需要一些额外的类型转换(例如,
let typed_value: MyType = any_value.extract()?;
)。 - 虽然实现了过程宏,但它可能无法在许多情况下正常工作。因此,建议尽可能使用构建脚本。
许可证
本项目采用双许可协议,以兼容 Rust 项目,可在MIT或Apache 2.0许可证下使用。
贡献
除非您明确声明,否则根据 Apache-2.0 许可证定义,您提交的任何有意包含在工作中的贡献,都将按上述方式双许可,不附加任何额外条款或条件。
依赖项
~5–11MB
~122K SLoC