如何分离Rust库与其包装的PyO3导出Python扩展?

-1 投票
1 回答
26 浏览
提问于 2025-04-12 23:27

我有一个用Rust写的库,它提供了一些在其他Rust程序中很有用的功能。此外,我还想把这些功能作为Python的扩展提供(使用pyo3和setuptools-rust,虽然大部分工具是通用的)。

文档中的例子都展示了一个单一的库。这意味着,任何构建这个Rust库并在Rust生态系统中使用它的人,都需要Python的头文件,并且还会得到导出的Python模块。

我该如何将这个库分开,使它成为一个独立的Rust库,提供Rust中的功能,同时又有一个Python包,提供Python中的功能呢?

1 个回答

-1

经过一些实验,我找到了一个使用 Rust 工作区的设置:

目录结构:

FizzBuzz
- fizzbuzz
  - src
    - lib.rs
  - tests
    - test_....rs
    ...
  - Cargo.toml
- fizzbuzzo3
  - src
    - lib.rs
  - Cargo.toml
- fizzbuzzpy
  - fizzbuzzpy
    - __init__.py
    - fizzbuzzer.py
  - tests
    - test_fizzbuzzo3.py
    - test_fizzbuzzpy.py
- Cargo.toml
- pyproject.toml

其中:

  • fizzbuzz 目录包含纯 Rust 的实现
  • fizzbuzzo3 目录包含为 Python 封装的 pyo3 版本
  • fizzbuzzpy 目录包含额外的原生 Python 功能和 pytest 测试

配置文件:

fizzbuzz/Cargo.toml
[package]
name = "fizzbuzz"
version = "0.2.1"
edition = "2021"

[lib]
name = "fizzbuzz"
path = "src/lib.rs"
crate-type = ["rlib"]  # cdylib required for python import, rlib required for rust tests.
fizzbuzzo3/Cargo.toml
[package]
name = "fizzbuzzo3"
version = "0.1.0"
edition = "2021"

[lib]
name = "fizzbuzzo3"
path = "src/lib.rs"
crate-type = ["cdylib"]  # cdylib required for python import, rlib required for rust tests.

[dependencies]
pyo3 = { version = "0.21.0-beta.0" }
fizzbuzz = { path = "../fizzbuzz" }
./Cargo.toml
[workspace]

members = [
    "fizzbuzz",
    "fizzbuzzo3"
]

resolver = "2"
pyproject.toml
[build-system]
requires = ["setuptools", "setuptools-rust"]
build-backend = "setuptools.build_meta"

[project]
name = "fizzbuzz"
description = "An implementation of the traditional fizzbuzz kata"
...
[tool.setuptools.packages.find]
where = ["fizzbuzzpy"]

[[tool.setuptools-rust.ext-modules]]
# Private Rust extension module to be nested into the Python package
target = "fizzbuzzo3"           # The last part of the name (e.g. "_lib") has to match lib.name in Cargo.toml,
                                # but you can add a prefix to nest it inside of a Python package.
path = "fizzbuzzo3/Cargo.toml"  # Default value, can be omitted
binding = "PyO3"                # Default value, can be omitted

[project.optional-dependencies]
dev = [
    "black",
    "pytest",
    "pytest-doctest-mkdocstrings",
    "ruff",
    ]

如何操作:

  • 构建并测试纯 Rust 实现: ~/Fizzbuzz/fizzbuzz$ cargo test -v
  • 构建并单元测试 Python 绑定: ~/Fizzbuzz/fizzbuzzo3$ cargo test -v
  • 运行所有 Rust 测试: ~/Fizzbuzz$ cargo test -v
  • 在 Python 中构建、安装并测试 Rust 绑定: (.venv) ~/Fizzbuzz$ pip install -e .[dev] && pytest

如果你想更详细地了解完整的代码库,可以查看: MusicalNinjaDad/FizzBuzz

撰写回答