Python 有强于 Bash 的语法检查,但是,不适合一些高性能的场景,所以,很多时候会作为胶水语言使用。而且,由于 CPython 作为事实标准,使得其兼容 C/C++ 要容易的多,所以,通常高性能场景下会通过 C/C++ 进行重构。
这里介绍 Python 如何调用 Rust 的实现。
简介
社区有个很不错的库 PyO3 可以用来在 Rust 和 Python 之间相互调用。
示例
为了简化处理,同时提供了 maturin 工具,可以通过 pip install maturin
安装。
----- 如下新建目录,或者在某目录下 init 初始化
$ maturin new pyex
----- 其中Cargo.toml关键内容如下
$ cat Cargo.toml
[lib]
name = "pyex"
crate-type = ["cdylib"]
[dependencies]
pyo3 = {version="0.22.0", features=["extension-module"]}
----- 会自动生成如下文件
$ cat src/lib.rs
use pyo3::prelude::*;
/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
/// A Python module implemented in Rust.
#[pymodule]
fn pyex(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum, m)?)?;
Ok(())
}
----- 可以直接编译
$ cargo build
此时会在 target/debug
目录下生成 libpyex.so
文件,然后切换到上述 target/debug
目录,并将源文件重命名为 pyex.so
文件,然后通过如下方式调用。
$ python
>>> import pyex
>>> pyex.sum(1, 2)
'3'
Maturin
为了简化,可以使用 maturin
命令,注意,其依赖 virtualenv
或者 venv
环境,更多可以参考 Python 虚拟环境详解 内容,如下是常用的命令。
----- 启动 Python 虚拟环境,可以防止对老环境的污染
$ python -m venv rust
----- 后续可以直接使用该命名
$ source rust/bin/activate
----- 编译,会在 target/wheels 目录下生成 *.whl 文件
(rust) $ maturin build
----- 会自动安装到 Python 环境中,可以通过 pip list 查看
(rust) $ maturin develop
----- 生成 .pyd 文件,可以在 Python 中直接导入
(rust) $ maturin develop --skip-install
----- 打包生成 .whl 可发布的文件
(rust) $ maturin develop --release
然后就可以使用上述的方式验证,此时不需要再切换目录、重命名文件。
常用技巧
错误处理
如上示例的函数会返回 PyResult
结果,在 src/err/mod.rs
文件中定义如下。
pub struct PyErr {
state: UnsafeCell<Option<PyErrState>>,
}
pub type PyResult<T> = Result<T, PyErr>;
常规的,可以通过 PyOSError::new_err("error")
类似方式生成,如果有些三方库自定义的错误类型,简单的可以通过 map_err
函数进行映射,详见 Rust 中的错误处理 中的介绍。
当三方库的错误类型比较多时,上述 map_err
方式处理起来会比较繁琐,而受限于 Rust 的 Orphan Rule
的限制,无法对一个三方的错误实现某个三方的特征,也就是如下的方式会报错。
impl From<hello::Error> for PyErr {
fn from(err: hello::Error) -> Self {
Self(err)
}
}
此时就需要引入一个中间的错误类型进行转换,详见 Error Handling 的介绍。
参考
- PyO3.rs 官方的帮助文档。