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日

#264FFI

Download history 2/week @ 2024-04-14 10/week @ 2024-04-21 7/week @ 2024-04-28 176/week @ 2024-05-05 27/week @ 2024-05-12 49/week @ 2024-05-19 34/week @ 2024-05-26 19/week @ 2024-06-02 19/week @ 2024-06-09 15/week @ 2024-06-16 19/week @ 2024-06-23 2/week @ 2024-06-30 3/week @ 2024-07-07 24/week @ 2024-07-14 7/week @ 2024-07-21 57/week @ 2024-07-28

每月 91 次下载
6 个 Crates 中使用 (直接使用 2 个)

MIT/Apache

185KB
4K SLoC

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.5 版本的 pyo3_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 毫秒(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项目兼容,许可证可以是MITApache 2.0

贡献

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

依赖关系

~4–9.5MB
~96K SLoC