【Rust中级教程】题外话:Rust + Python联合编程(基于Maturin)
1. 为啥要利用Rust加速Python?
我相信大部分都知道Rust和Python这两个语言的利弊:
- Rust性能好,但是代码难写
- Python代码好写,但是性能不好
那为什么不把两者结合起来呢?让Python在顶层调用,用Rust写性能敏感的模块。
2. 需要的软件及环境
我这里使用的是JetBrains的RustRover,目前是免费的。有的人会觉得它不好用,用VScode也是可以的。
打开RustRover,由于我们需要写Python的代码块,所以我们得先安装Python插件。在插件里搜索Python Community Edition安装即可。
注:安装好后需要根据IDE的提示重启IDE这个插件才会生效。
3. 基础教程
我们创建一个项目,由于RustRover会在创建项目之后会自动配置Cargo,生成Cargo那一堆东西。所以我们得先把里面的所有东西全删掉。
由于我们是利用Rust加速Python,所以我们得把整个项目当做一个Python项目而不是Rust项目来做。在终端中(可以点击IDE左下角的终端标志来在IDE中输入终端命令),使用下面的代码创建一个Python虚拟环境:
python -m venv .venv
.venv
是我起的名字,你可以替换
创建好后的项目应该长这样:
接下来我们需要激活这个虚拟环境,在终端使用如下指令:
For MacOS/Linux:
source .venv/bin/activate
For Windows:
.\.venv\Scripts\avtivate
执行之后如果你的命令行前缀多了(.venv)
(.venv
是我起的名字,如果你换用其它名字就会不一样),就是没问题的。
注意:如果你的环境用了conda得先退掉(你的命令行前缀有(base)
标记),因为maturin不允许环境变量中同时存在Python venv 和Conda的激活环境,导致冲突。输入以下指令以退出conda环境:
conda deactivate
maturin
是python的一个包,我们得在这个虚拟环境中先安装:
pip install maturin
下载好之后就可以把这个项目整成Rust和Python结合的样子了。在终端输入指令:
maturin init
这时候它会给你3个选项,你选择第一个按回车即可:
这时候注意看项目文件结构,就既有Python也有Rust了:
- Rust文件放在
src
目录下 - Python文件可以在项目的顶层目录下创建
我们先创建main.py
这个Python文件:
打开src
文件夹,看看lib.rs
,它里面已经有代码了:
use pyo3::prelude::*;
/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
/// A Python module implemented in Rust.
#[pymodule]
fn maturin_test(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
- 写在
#[pyfunction]
标注下的函数(比如sum_as_string
)是你想要给Python用的函数,但是Python不能直接看到,得先把这个给#[pymodule]
标注下的函数(比如maturin_test
),让它调用一遍,这样Python就会把sum_as_string
视作为maturin_test
这个库下的一个函数 #[pymodule]
标注下的函数会在Python中显示为一个库
但目前这样Python还看不见,我们得先让maturin
给我们编译Rust代码供Python代码调用。在终端输入:
maturin develop
之后maturin
就会开始编译你的Rust代码成.dll
文件,然后改为.pyd
文件(MacOS/Linus上会生成.so
文件)
编译完之后你看.venv/bin
目录下多了一个maturin_test
文件夹,这个文件夹会被识别为一个包供Python代码使用。
再写Python代码之前还有最后一步:配置解释器。打开设置,搜索“Python解释器”,点击“添加解释器”,接着点击“添加本地解释器”,选择“选择现有”,添加这个项目路径下的的Python解释器即可。
大功告成,我们来随便写点验证一下吧:
import maturin_test
num = maturin_test.sum_as_string(1, 1)
print(num)
输出:
2
没有问题,这正是我们想要的结果
如果你的代码在这一步报错,那大概率是需要你手动编辑Python运行设置。点击右上角"Run"右边的三角尖,选择“编辑配置”
打开之后会进入“运行/调试配置”窗口,这时候点击左上角的加号,添加Python配置,它的配置可以直接抄图中的:
点击“确定”,然后退出这个界面,再点击运行即可。
4. 添加存根(可选)
如果你仔细看刚才写代码,会发现有一处波浪线警告:
警告信息是找不到’sum_as_string’函数。这是因为.pyd
(在MacOS/Linux上是.so
)文件里面存的是二进制源码,Python解释器不可能从中找出具体有哪些函数在这个库里,所以就报警。
这并不影响实际写代码。但是如果你跟我一样是一位强迫症患者,就可以通过添加存根的方式来解决问题。Python 的 存根(Stub) 是用于类型提示的.pyi
文件,它只包含函数、类和变量的声明(不包含实现),用于静态类型检查工具来提供类型信息。
在项目的顶层目录下添加一个Python存根文件即可,名称与maturin_test
这个库的名字保持一致:
在里面这么写:
def sum_as_string(a: int, b: int) -> str: ...
这时候就不会有报警了。如果你以后还要在Rust代码里再加函数并在Python代码中运用,就到存根文件里写个函数头即可。
5. 实战(例子)
询问用户输入 n,然后计算第 n 个斐波那契数。
纯Python的实现:
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == "__main__":
n = int(input("请输入要计算的斐波那契数列位置 n: "))
result = fibonacci(n)
print(f"Fibonacci({n}) = {result}")
纯Rust的实现:
use std::io;
fn fibonacci(n: usize) -> usize {
let mut a = 0;
let mut b = 1;
for _ in 0..n {
let temp = b;
b = a + b;
a = temp;
}
a
}
fn main() {
println!("请输入要计算的斐波那契数列位置 n:");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("读取输入失败");
let n: usize = input.trim().parse().expect("请输入一个有效的正整数");
let result = fibonacci(n);
println!("Fibonacci({}) = {}", n, result);
}
可以看到,计算斐波那契数列Rust代码性能占优,但是Rust询问用户输入的部分又过于繁琐;Python在这两点上正好相反。我们就可以依靠maturin
各取所长。
前面的配置部分我不再重复了,我们从写lib.rs开始:
use pyo3::prelude::*;
#[pyfunction]
fn fibonacci(n: usize) -> usize {
let mut a = 0;
let mut b = 1;
for _ in 0..n {
let temp = b;
b = a + b;
a = temp;
}
a
}
#[pymodule]
fn maturin_test(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(fibonacci, m)?)?;
Ok(())
}
- 直接把Rust的斐波那契数列函数粘过来即可,放在
#[pyfunction]
标注下 #[pymodule]
下写了函数maturin_test,maturin_test又调用了fibonacci
这个函数。这样Python代码就会把maturin_test
视作为一个包,而fibonacci
会被视作这个包下的一个函数
编译一下Rust代码,输入指令:
maturin develop
在存根文件maturin_test.pyi
中写上函数的函数头:
def fibonacci(n: int) -> int: ...
在main.py
下这么写:
import maturin_test
if __name__ == "__main__":
n = int(input("请输入要计算的斐波那契数列位置 n: "))
result = maturin_test.fibonacci(n)
print(f"Fibonacci({n}) = {result}")
输出:
请输入要计算的斐波那契数列位置 n: 40
Fibonacci(40) = 102334155