Python调用Rust研究 方法 有使用maturin
将rust编译成so/dll文件,并在python中调用so/dll文件
使用maturin 先创建python项目的虚拟环境
激活项目虚拟环境
在虚拟环境里装maturin
使用它初始化python项目
然后开始编写rust,可以在它的基础上进行更改
然后运行
然后python这里就能调用了
直接让rust编译成二进制文件 PyO3: Python 调用 Rust 代码 - 编程宝库 (codebaoku.com)
这个经过测试可以实现windows上,python调用rust编译成的dll,将dll改名成pyd。
但一定要保证最终 #[pymodule] 修饰的那个函数的名字,和最终python里import的名字保持一样
还要用windows11本身的cmd黑色窗口来运行,用rustRover里面的编译指令可能会出问题。
注意:打包成so文件的,必须要用linux环境运行,并且打包出来的so文件前面会多一个lib三个字符,需要去掉。
新问题: rust这里代码量多了如何组织?分别打包成多个不同的模块,还是集成在一起?
rust这里写的类型在python那里用的时候有什么局限性?(int变成i32会有大小限制)
这种变成二进制文件的引用调用中间会不会有速度损耗?相比纯python编写的逻辑,性能提升了多少?
除了调用函数以外,还有没有其他的东西?比如从rust这里拿到一个类,提供一个类让python这里去用(很麻烦)
类型局限性 1 2 3 4 5 6 7 Traceback (most recent call last): File "D:\Projects\Project-Test\python-rust-so\main.py", line 12, in <module> main() File "D:\Projects\Project-Test\python-rust-so\main.py", line 7, in main print(rust_rover_test.sum_as_string(100000000000000000000000000000000000000000, 1000_0000_0000_0000)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ OverflowError: int too big to convert
在python端传入太大的数字会崩掉。
pyo3的官方文档
Introduction - PyO3 user guide
发现一个警告:最开始的模块绑定所有函数的代码是这样的:
1 2 3 4 5 6 #[pymodule] fn rust_rover_test (_py: Python, m: &PyModule) -> PyResult<()> { m.add_wrapped (wrap_pyfunction!(sum_as_string))?; m.add_wrapped (wrap_pyfunction!(sum_i32))?; Ok (()) }
后来发现原来是API过时了,变了,参数应该改成
1 2 3 4 5 6 7 8 #[pymodule] fn rust_rover_test (m: &Bound<'_ , PyModule>) -> PyResult<()> { m.add_function (wrap_pyfunction!(sum_as_string, m)?)?; m.add_function (wrap_pyfunction!(sum_i32, m)?)?; Ok (()) }
这个是在pyo3官网上看到的
对数据类型的对接探索 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 use std::collections::{HashMap, HashSet};use pyo3::prelude::*;use pyo3::wrap_pyfunction;#[pyfunction] fn sum_as_string (a: usize , b: usize ) -> PyResult<String > { Ok ((a + b).to_string ()) } #[pyfunction] fn sum_i32 (a: i32 , b: i32 ) -> PyResult<i32 > { Ok (a + b) } #[pyfunction] fn sum_f32 (a: f32 , b: f32 ) -> PyResult<f32 > { Ok (a + b) } #[pyfunction] fn merge_lists (a: Vec <i32 >, b: Vec <i32 >) -> PyResult<Vec <i32 >> { Ok (a.iter ().chain (b.iter ()).cloned ().collect ()) } #[pyfunction] fn merge_tuples (a: (i32 , i32 ), b: (i32 , i32 )) -> PyResult<(i32 , i32 )> { Ok ((a.0 + b.0 , a.1 + b.1 )) } #[pyfunction] fn merge_dicts (a: HashMap<String , i32 >, b: HashMap<String , i32 >) -> PyResult<HashMap<String , i32 >> { let mut result = a; for (k, v) in b { result.insert (k, v); } Ok (result) } #[pyfunction] fn merge_sets (a: HashSet<i32 >, b: HashSet<i32 >) -> PyResult<HashSet<i32 >> { Ok (a.union (&b).cloned ().collect ()) } #[pymodule] fn rust_rover_test (m: &Bound<'_ , PyModule>) -> PyResult<()> { m.add_function (wrap_pyfunction!(sum_as_string, m)?)?; m.add_function (wrap_pyfunction!(sum_i32, m)?)?; m.add_function (wrap_pyfunction!(sum_f32, m)?)?; m.add_function (wrap_pyfunction!(merge_lists, m)?)?; m.add_function (wrap_pyfunction!(merge_tuples, m)?)?; m.add_function (wrap_pyfunction!(merge_dicts, m)?)?; m.add_function (wrap_pyfunction!(merge_sets, m)?)?; Ok (()) }
对组合数据类型的摸索 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 47 48 49 50 51 52 53 54 55 56 57 58 use std::collections::{HashMap, HashSet};use pyo3::prelude::*;use pyo3::wrap_pyfunction;#[pyfunction] fn sum_as_string (a: usize , b: usize ) -> PyResult<String > { Ok ((a + b).to_string ()) } #[pyfunction] fn sum_i32 (a: i32 , b: i32 ) -> PyResult<i32 > { Ok (a + b) } #[pyfunction] fn sum_f32 (a: f32 , b: f32 ) -> PyResult<f32 > { Ok (a + b) } #[pyfunction] fn matrix_sum (matrix: Vec <Vec <i32 >>) -> PyResult<i32 > { let mut sum = 0 ; for row in matrix { for num in row { sum += num; } } Ok (sum) } #[pyfunction] fn dict_to_hashmap (dict_list: Vec <HashMap<i32 , Vec <i32 >>>) -> PyResult<HashMap<i32 , HashSet<i32 >>> { let mut hashmap = HashMap::new (); for mut dict in dict_list { for (key, value) in dict.drain () { hashmap.entry (key).or_insert_with (HashSet::new).extend (value); } } Ok (hashmap) } #[pymodule] fn rust_rover_test (m: &Bound<'_ , PyModule>) -> PyResult<()> { m.add_function (wrap_pyfunction!(sum_as_string, m)?)?; m.add_function (wrap_pyfunction!(sum_i32, m)?)?; m.add_function (wrap_pyfunction!(sum_f32, m)?)?; m.add_function (wrap_pyfunction!(matrix_sum, m)?)?; m.add_function (wrap_pyfunction!(dict_to_hashmap, m)?)?; Ok (()) }
实际上,类型转换官网上有一个表格:
Rust 类型到 Python 类型的映射 - PyO3 用户指南 — Mapping of Rust types to Python types - PyO3 user guide
实际上运行的时候,python参数会转换成rust参数,然后在rust代码中运行。这个是有一点点转换成本的。但是rust的高性能会极大的弥补这成本。
代码组织问题 直接把src/lib.rs 当成main文件,只写一个注册函数的总函数。其他的引入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 use std::collections::{HashMap, HashSet};use pyo3::{pyfunction, PyResult};#[pyfunction] pub fn dict_to_hashmap (dict_list: Vec <HashMap<i32 , Vec <i32 >>>) -> PyResult<HashMap<i32 , HashSet<i32 >>> { let mut hashmap = HashMap::new (); for mut dict in dict_list { for (key, value) in dict.drain () { hashmap.entry (key).or_insert_with (HashSet::new).extend (value); } } Ok (hashmap) }
比如这个函数,前面就要注意加上pub关键字。
然后lib.rs里面,直接引入
1 2 3 mod dict_functions;use dict_functions::dict_to_hashmap;
直接调用就可以了。比较好的是,这个函数的rust里的三斜杠注释文档会能被python这里提取识别到
1 print (rust_rover_test.dict_to_hashmap.__doc__)
即使有了pyi文件,pyi文件里写的python文档注释,是给pycharm看的,鼠标悬浮在函数上显示用的,但实际上 __doc__
和help函数,调用的是rust里面的文档注释和函数参数名。
但是在python调用端,如何像这样含有很多点调用,看起来有组织结构?
1 2 res = DictUtils.dict_to_hashmap({...}) res = GameBoardUtils.transform_board([...])
性能测试 写了一个五子棋判断输赢的代码,看rust比python快多少
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 from random import randintfrom time import perf_counterimport rust_rover_testdef generate_random_board (): """ 生成随机落子的15*15棋盘 0表示空格,1表示黑子,2表示白子 :return: """ return [[randint(0 , 2 ) for _ in range (15 )] for _ in range (15 )] def main (): boards = [generate_random_board() for _ in range (10_0000 )] print ("Start testing..." ) t1 = perf_counter() res = [0 , 0 , 0 ] for board in boards: winner = check_winner(board) res[winner] += 1 t2 = perf_counter() print (res) print (f"Time used: {t2 - t1:.6 f} s" ) res = [0 , 0 , 0 ] t1 = perf_counter() for board in boards: winner = rust_rover_test.get_score(board) res[winner] += 1 t2 = perf_counter() print (res) print (f"Time used: {t2 - t1:.6 f} s" ) pass
测试代码是这样的。
具体检测原理的代码就不写了,因为是直接拿AI写的,懒得自己写了。但AI写的两个代码跑的结果居然不一样。不去纠结这个细节了。
测试结果显示这样的代码,rust比python快四倍。python用0.166秒,rust用0.4817秒
Author:
Littlefean
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
仅个人观点,buddy up!