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日 |
#264 在 FFI 中
每月 91 次下载
在 6 个 Crates 中使用 (直接使用 2 个)
185KB
4K SLoC
pyo3_bindgen
通过 PyO3 自动生成 Rust FFI 绑定到 Python 模块。Python 模块将递归地进行分析以生成具有相同结构的 Rust 绑定,包括所有公共类、函数、属性和常量。任何可用的文档字符串和类型注解也将保留在其 Rust 等价物中。
以下展示了生成的 Rust 函数签名及其预期用法示例。当然,在大多数情况下,手动将生成的绑定部分包装在更符合 Rust 习惯的 API 中可能是有益的。
源代码 (Python) | 生成的代码 (Rust) |
---|---|
|
|
该项目旨在简化现有 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 毫秒(0.66k 总行数)os
: 8.38 毫秒(3.88k 总行数)numpy
: 1.02 秒(294k 总行数)torch
: 7.05 秒(1.08M 总行数)
- 只要目标 Python 模块可以成功导入,绑定生成就不会 panic。如果它确实这样做,请 报告 这作为一个错误。
- 生成的绑定应该始终可编译,并且在Rust中使用。如果您遇到任何问题,请考虑手动修复绑定中的问题部分,并请报告此问题作为错误。
- 然而,生成的绑定基于目标Python模块的反射。因此,生成的绑定的完整性和正确性直接取决于目标Python模块的结构、类型注解和docstrings的质量。理想情况下,生成的绑定应被视为不安全的,并作为创建安全和惯用Rust API的起点。如果您发现生成的绑定中存在错误或缺失的部分,请报告。
- 并非所有Python类型都已映射到其Rust等效类型。因此,在使用生成的绑定时可能需要额外的类型转换(例如:
let typed_value: MyType = any_value.extract()?;
)。 - 虽然已经实现,但过程宏可能在不许多情况下工作。因此,建议尽可能使用构建脚本。
许可证
本项目采用双许可,以与Rust项目兼容,许可证可以是MIT或Apache 2.0。
贡献
除非您明确声明,否则根据Apache-2.0许可证定义的,任何有意提交以包含在作品中的贡献,都将根据上述方式双许可,没有任何额外条款或条件。
依赖关系
~4–9.5MB
~96K SLoC