如何从 Python 调用 Rust 异步方法?

How to call Rust async method from Python?

我想在 Python 中使用 Rust 异步方法。 我正在尝试使用 PyO3 or rust-cpython.

例如,对于同步 Rust 函数,我可以使用,

#[pyfunction]
fn myfunc(a: String) -> PyResult<String> {
   let mut contents = String::new();
   contents = a.to_string() + " appended";
   Ok((contents))
}

#[pymodule]
fn MyModule(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(urlshot))?;
    Ok(())
}

对于异步方法,我该怎么做?比如我想在Python,

中调用下面的方法
async fn hello_world() {
    println!("hello, world!");
}

如果你想用 Python 来控制 Rust 的异步函数,我认为它不会起作用(或者至少它很复杂,因为你需要连接两个不同的 future机制)。对于异步函数,Rust 编译器将维护一个状态机以在 await 的控制下正确管理协程 运行。这是 Rust 应用程序的内部状态,Python 无法触及它。同样Python解释器也有Rust无法触及的状态机。

我发现 this topic 关于如何使用 FFI 导出异步函数。主要思想是将异步包装在 BoxFuture 中,让 C 控制将其返回给 Rust 的时间。但是,您不能在 PyO3 中使用 BoxFuture,因为它的 pyfunction 宏不能将函数 returns BoxFuture 转换为 Python 回调。您可以尝试使用 FFI 创建一个库并使用 python 的 cffi 模块来加载它。

由于没有简单的方法来解决这个问题(至少,我没有找到),我将我的异步方法转换为同步方法。并在 Python 方面将其称为

async fn my_method(s: &str) -> Result<String, Error> {
    // do something
}

#[pyfunction]
fn my_sync_method(s: String) -> PyResult<String> {
    let mut rt = tokio::runtime::Runtime::new().unwrap();
    let mut contents = String::new();
    rt.block_on(async {
        result = format!("{}", my_sync_method(&s).await.unwrap()).to_string();
    });
   Ok((result))
}

#[pymodule]
fn MyModule(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(my_sync_method))?;
    Ok(())
}

已编辑

Cargo.toml文件中,我添加了如下依赖,


[dependencies.pyo3]
git = "https://github.com/PyO3/pyo3"
features = ["extension-module"]

经过运行cargo build --release,生成target/release/libMyModule.so二进制文件。将其重命名为 MyModule.so,现在可以从 Python.

导入
import MyModule
result = MyModule.my_sync_method("hello")

使用 setuptools-rust,我可以将其捆绑为普通的 Python 包。

以上所有代码和命令都在新发布的 Linux Mint 20 上进行了测试。在 MacOS 上,二进制文件将为 libMyModule.dylib.