Rust编写Python扩展
https://github.com/PyO3/pyo3
pyo3.rs
简单示例
新建 pyo3_test 文件夹,在文件夹下进行操作
执行 cargo new --lib simple_calc
新建 simple_calc 文件夹:
修改 simple_calc/Cargo.toml
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | [package]
name = "simple_calc"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "simple_calc"
crate-type = ["cdylib"]
[dependencies.pyo3]
version = "0.13.2"
features = ["extension-module"]
|
修改 simple_calc/src/lib.rs
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
#[pyfunction]
fn add(a: usize, b: usize) -> PyResult<usize> {
Ok(a + b)
}
#[pyfunction]
fn hello() -> PyResult<()> {
println!("Hello world!");
Ok(())
}
/// A Python module implemented in Rust.
#[pymodule]
fn simple_calc(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(add, m)?)?;
m.add_function(wrap_pyfunction!(hello, m)?)?;
Ok(())
}
|
On Windows and Linux, you can build normally with cargo build --release
.
On macOS, you need to set additional linker arguments.
One option is to compile with cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup
我是在 linux 下测试的,所以在 simple_calc
目录下执行 cargo build --release
While developing, you can symlink (or copy) and rename the shared library from the target folder:
On MacOS, rename libstring_sum.dylib to string_sum.so,
on Windows libstring_sum.dll to string_sum.pyd, and
on Linux libstring_sum.so to string_sum.so.
Then open a Python shell in the same folder and you'll be able to import string_sum.
在 pyo3_test 目录下新建 py_run 文件夹
将 simple_calc/target/release/libsimple_calc.so
复制到 py_run 文件夹下
将 py_run
文件夹下的 libsimple_calc.so
重命名为 simple_calc.so
在 py_run
文件夹下新建 test.py
:
| import simple_calc
simple_calc.hello()
print(simple_calc.add(1, 2))
|
执行 python test.py
输出结果:
对象
lib.rs
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
#[pyclass]
struct MyClass {
num: i32,
}
#[pymethods]
impl MyClass {
#[new]
fn new(num: i32) -> Self {
MyClass { num }
}
pub fn add(&self, a: i32, b: i32) -> PyResult<i32> {
Ok(self.num + a + b)
}
}
#[pymodule]
fn trade_rs(py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<MyClass>()?;
Ok(())
}
|
| import trade_rs
c = trade_rs.MyClass(1)
print(c.add(1, 2))
|
输出:
异步
Cargo.toml
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | [package]
name = "trade_rs"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "trade_rs"
crate-type = ["cdylib"]
[dependencies]
async-std = "1.9.0"
pyo3-asyncio = { version = "0.13.3", features = ["async-std-runtime"] }
[dependencies.pyo3]
version = "0.13.2"
features = ["extension-module"]
|
src/lib.rs
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use std::time::Duration;
async fn rust_sleep() {
async_std::task::sleep(Duration::from_secs(10)).await;
}
#[pyfunction]
fn call_rust_sleep(py: Python) -> PyResult<PyObject> {
pyo3_asyncio::async_std::into_coroutine(py, async move {
println!("await sleep 10s");
rust_sleep().await;
println!("finish");
Python::with_gil(|py| Ok(py.None()))
})
}
/// A Python module implemented in Rust.
#[pymodule]
fn trade_rs(py: Python, m: &PyModule) -> PyResult<()> {
pyo3_asyncio::try_init(py);
m.add_function(wrap_pyfunction!(call_rust_sleep, m)?)?;
Ok(())
}
|
| import trade_rs
import asyncio
async def run():
await trade_rs.call_rust_sleep()
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
|
输出结果:
在 python 包中使用
可以将其复制到与根 __init__.py
同一目录
假设项目名称为 my_test, rust 包名称为 rust_test,使用示例:
将 rust_test.so 放到 my_test 目录下
| from my_test import rust_test
|
rust 异步返回 json 字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #[derive(Deserialize, Serialize)]
pub struct MyClass {
pub id: i32
}
pub fn get_msg(&self, py: Python) -> PyResult<PyObject> {
let rx = self.send2py_rx.clone();
pyo3_asyncio::async_std::into_coroutine(py, async move {
if let Some(rx) = rx {
let mut raw_rx = rx.lock().await;
let msg = raw_rx.next().await;
let myclass = MyClass { id: 1 };
let json_class = serde_json::json!(&myclass).to_string();
Python::with_gil(|py| Ok(PyObject::from(PyString::new(py, &json_class))))
} else {
let myclass = MyClass { id: 2 };
let json_class = serde_json::json!(myclass).to_string();
Python::with_gil(|py| Ok(PyObject::from(PyString::new(py, &json_class))))
}
})
}
|
如果导出的class中有异步方法,需要对其进行一个同步封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 | use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
#[pyclass]
struct SyncClass {
pub myclass: MyClass,
}
#[pymethods]
impl SyncClass {
#[new]
pub fn new(num: i32) -> Self {
SyncClass { myclass: MyClass { num } }
}
pub fn add(&self, a: i32, b: i32) -> i32 {
self.myclass.num + a + b
}
pub fn my_num(&self) -> i32 {
self.myclass.num
}
}
struct MyClass {
num: i32,
}
impl MyClass {
fn new(num: i32) -> Self {
MyClass { num }
}
pub fn add(&self, a: i32, b: i32) -> PyResult<i32> {
Ok(self.num + a + b)
}
pub async fn async_test(&self) {
}
}
#[pymodule]
fn hhh(py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<SyncClass>()?;
Ok(())
}
|
| import hhh
c = hhh.SyncClass(1)
print(c.my_num())
print(c.add(1, 2))
|
输出: