完全固定,照抄就行
Cargo.toml
toml
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
pyo3 = { version = "0.27" }
[features]
default = []
extension-module = ["pyo3/extension-module"]
pyproject.toml
toml
[build-system]
requires = ["maturin>=1.7,<2.0"]
build-backend = "maturin"
[tool.maturin]
features = ["extension-module"]
这些永远不用动。
src/lib.rs ------ 唯一需要你写的部分
结构是固定的,但内容是你的。看这个 repo 的实际写法:
rust
// [src/lib.rs#L574-L579]
#[pymodule]
fn rustbpe(m: &Bound<'_, PyModule>) -> PyResult<()> {
pyo3_log::init();
m.add_class::<Tokenizer>()?; // 注册你的类
Ok(())
}
你只需要关心两件事:
1. 函数名 = 模块名
fn rustbpe(...) 这里的函数名,就是 Python 里 import 的名字。你写 fn mylib(...) 就 import mylib。
2. 往模块里注册你的东西
| 想暴露的东西 | 写法 |
|---|---|
| 一个类 | m.add_class::<MyStruct>()? + struct 上加 #[pyclass] |
| 一个函数 | m.add_function(wrap_pyfunction!(my_fn, m)?)? + fn 上加 #[pyfunction] |
| 一个常量 | m.add("MY_CONST", 42)? |
最小可运行示例
假设你要写一个 Python 可以调用的 add(a, b) 函数和一个 Counter 类:
rust
use pyo3::prelude::*;
#[pyfunction]
fn add(a: i64, b: i64) -> i64 {
a + b
}
#[pyclass]
struct Counter {
value: i64,
}
#[pymethods]
impl Counter {
#[new]
fn new() -> Self {
Counter { value: 0 }
}
fn increment(&mut self) {
self.value += 1;
}
fn get(&self) -> i64 {
self.value
}
}
#[pymodule]
fn mylib(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(add, m)?)?;
m.add_class::<Counter>()?;
Ok(())
}
Python 端:
python
import mylib
print(mylib.add(1, 2)) # 3
c = mylib.Counter()
c.increment()
print(c.get()) # 1
总结一句话
配置文件照抄,
#[pymodule]函数名改成你的模块名,然后把你的#[pyclass]/#[pyfunction]注册进去。其他的 Maturin 帮你搞定。
想了解更多 PyO3 绑定的细节和这个 repo 是怎么处理类型转换、错误的: