#python-module #python-bindings #pyo3 #bindings-generator #python #generated-bindings #cli-tool

app pyo3_bindgen_cli

用于自动生成Rust绑定到Python模块的CLI工具

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

MIT/Apache

38KB
66

pyo3_bindgen

crates.io docs.rs Rust deps.rs codecov.io

通过PyO3自动生成Rust FFI绑定到Python模块。Python模块递归分析以生成与所有公共类、函数、属性和常量结构相同的Rust绑定。任何可用的文档字符串和类型注解也保存在它们的Rust等效项中。

以下显示了生成的Rust函数签名及其预期用法示例。当然,在大多数情况下,手动将生成的绑定的部分包装在更符合Rust语法的API中可能是有益的。

源代码(Python) 生成的代码(Rust)
 
def answer_to(question: str) -> int:
  """Returns answer to a question."""

  return 42

 

 
def main():
  assert answer_to("life") == 42


if __name__ == "__main__":
  main()
 
/// Returns answer to a question.
pub fn answer_to<'py>(
  py: ::pyo3::Python<'py>,
  question: &str,
) -> ::pyo3::PyResult<i64> {
  ... // Calls function via `pyo3`
}

pub fn main() -> pyo3::PyResult<()> {
  pyo3::Python::with_gil(|py| {
    assert_eq!(
      answer_to(py, "universe")?, 42
    );
    Ok(())
  })
}

该项目旨在简化现有Python代码库集成或迁移到Rust。通过生成的绑定,您作为开发者可以立即访问Rust类型系统以及现代编译语言的无数其他好处。此外,整个来自crates.io的高质量crate库也供您使用。

单独生成的Rust代码与使用Python代码相比,不会提供任何性能优势。但是,如果您决定将代码库中性能关键的部分重写为纯Rust,它可以作为进一步优化的起点。

[!注意] 版本 0.5pyo3_bindgen 适配了在 pyo3 版本 0.21 中引入的新智能指针 pyo3::Bound<'py, T>。如果您需要旧的 "GIL Refs" API,请使用 0.4 版本的 pyo3_bindgen

概述

工作空间包含以下包

pyo3_bindgen 的功能

  • macros [实验性]: 启用来自 pyo3_bindgen_macros crate 的 import_python!
  • numpy [实验性]: 启用 Python numpy::ndarray 与 Rust numpy::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 项目,可在MITApache 2.0许可证下使用。

贡献

除非您明确声明,否则根据 Apache-2.0 许可证定义,您提交的任何有意包含在工作中的贡献,都将按上述方式双许可,不附加任何额外条款或条件。

依赖项

~5–11MB
~122K SLoC