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日

#171FFI

Download history 13/week @ 2024-04-22 188/week @ 2024-05-06 10/week @ 2024-05-13 45/week @ 2024-05-20 29/week @ 2024-05-27 8/week @ 2024-06-03 17/week @ 2024-06-10 4/week @ 2024-06-17 13/week @ 2024-06-24 15/week @ 2024-07-15 1/week @ 2024-07-22 57/week @ 2024-07-29 8/week @ 2024-08-05

每月81次下载
用于 4 个 Crates(直接使用 2 个)

MIT/Apache

205KB
4K SLoC

pyo3_bindgen

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

通过 PyO3 自动生成 Rust FFI 绑定到 Python 模块。递归分析 Python 模块以生成与所有公共类、函数、属性和常量结构相同的 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 上的所有高质量 Crates 都可供您使用。

单独来看,生成的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 包的 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"] }

然后,在你的包的根目录中创建一个 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(())
}

之后,您可以通过在crate中的任何地方使用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功能的一个Cargo.toml清单中的macros功能的一个Cargo.toml清单中的macros功能的常规依赖项,并启用。

[dependencies]
pyo3_bindgen = { version = "0.5", features = ["macros"] }

随后,可以使用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在默认配置下的初步结果(测量:解析IO & 代码生成 | 未测量:生成绑定的编译,这需要很长时间)
    • 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。如果发生,请报告此为bug。
  • 生成的绑定应该始终可编译并在Rust中使用。如果遇到任何问题,请考虑手动修复绑定中的问题,并请报告此为bug。
  • 然而,生成的绑定基于目标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
~95K SLoC