RustPython与Rust代码互调指南:从Python调用Rust函数的3种方式
你是否在寻找一种高效的方式让Python与Rust代码无缝协作?当Python的灵活性遇到Rust的性能优势,会擦出怎样的火花?本文将深入探讨使用RustPython实现Python调用Rust函数的三种实用方法,帮助你解决跨语言交互中的性能瓶颈问题。读完本文,你将能够:
- 掌握RustPython中Rust与Python互调的核心原理
- 实现基本类型、复杂结构体和回调函数的跨语言传递
- 理解三种调用方式的适用场景和性能差异
- 通过完整示例代码快速上手实践
一、RustPython跨语言调用基础
1.1 什么是RustPython?
RustPython是一个用Rust编写的Python解释器(Python Interpreter written in Rust),它允许开发者在Rust环境中嵌入Python解释器,同时也支持将Rust函数暴露给Python环境调用。这种双向交互能力为性能敏感型Python应用提供了新的优化途径。
1.2 核心架构与工作原理
RustPython的跨语言调用基于以下核心组件:
- Virtual Machine(虚拟机):负责执行Python字节码,管理Python对象生命周期
- Native Module Bridge(原生模块桥):连接Python模块系统与Rust函数
- Type Conversion(类型转换):处理Python与Rust之间的数据类型映射
1.3 开发环境准备
首先,确保你的开发环境满足以下要求:
| 依赖项 | 版本要求 | 安装命令 |
|---|---|---|
| Rust | ≥1.65.0 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| Cargo | ≥1.65.0 | 随Rust安装 |
| RustPython | 最新版 | git clone https://gitcode.com/GitHub_Trending/ru/RustPython |
克隆仓库并构建项目:
# 克隆仓库
git clone https://gitcode.com/GitHub_Trending/ru/RustPython
cd RustPython
# 构建项目
cargo build --release
二、方式一:基本类型传递 - 原生函数调用
2.1 定义Rust原生函数
使用RustPython提供的宏系统,可以轻松将Rust函数暴露给Python环境。以下是一个处理基本数据类型的示例:
// examples/call_between_rust_and_python.rs (部分代码)
use rustpython::vm::{PyObject, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine, pyclass, pymodule};
#[pymodule]
mod rust_py_module {
use super::*;
use rustpython::vm::{PyObjectRef, convert::ToPyObject};
#[pyfunction]
fn rust_function(
num: i32, // 整数类型
s: String, // 字符串类型
python_person: PythonPerson, // 自定义Python类实例
_vm: &VirtualMachine,
) -> PyResult<RustStruct> { // 返回Rust结构体
println!(
"Calling standalone rust function from python passing args:
num: {},
string: {},
python_person.name: {}",
num, s, python_person.name
);
Ok(RustStruct {
numbers: NumVec(vec![1, 2, 3, 4]),
})
}
// ... 更多代码
}
关键宏说明:
#[pymodule]:标记一个Rust模块为Python可导入模块#[pyfunction]:将Rust函数暴露为Python可调用函数#[pyclass]:定义可在Python中使用的Rust类#[pymethod]:定义Rust类的方法,供Python调用
2.2 类型转换实现
Rust与Python之间的类型转换是通过实现特定trait完成的。对于自定义类型,需要实现ToPyObject(Rust到Python)和TryFromBorrowedObject(Python到Rust):
// 自定义数字向量类型
#[derive(Debug, Clone)]
struct NumVec(Vec<i32>);
// 实现Rust到Python的类型转换
impl ToPyObject for NumVec {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
// 将Vec<i32>转换为Python列表
let list = self.0.into_iter().map(|e| vm.new_pyobj(e)).collect();
vm.ctx.new_list(list).to_pyobject(vm)
}
}
// Python对象到Rust类型的转换
struct PythonPerson {
name: String,
}
impl<'a> TryFromBorrowedObject<'a> for PythonPerson {
fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
// 从Python对象中提取"name"属性
let name = obj.get_attr("name", vm)?.try_into_value::<String>(vm)?;
Ok(Self { name })
}
}
2.3 在Python中调用Rust函数
完成Rust函数定义后,可以在Python中直接导入并调用:
# examples/call_between_rust_and_python.py (部分代码)
from rust_py_module import RustStruct, rust_function
class PythonPerson:
def __init__(self, name):
self.name = name
def python_callback():
print("Python callback invoked from Rust!")
# 创建Python对象
person = PythonPerson("Peter Python")
# 调用Rust函数,传递基本类型和Python对象
result = rust_function(42, "This is a python string", person)
# 使用Rust返回的结构体
print("Received numbers from Rust:", result.numbers)
result.print_in_rust_from_python()
def take_string(string):
print("Python function received:", string)
2.4 调用流程与数据流向
三、方式二:复杂结构体传递 - 自定义类型映射
3.1 定义可在Python中访问的Rust结构体
通过#[pyclass]宏,可以定义能在Python中实例化和操作的Rust结构体:
// examples/call_between_rust_and_python.rs (部分代码)
#[pyattr]
#[pyclass(module = "rust_py_module", name = "RustStruct")]
#[derive(Debug, PyPayload)]
struct RustStruct {
numbers: NumVec, // 包含自定义NumVec类型
}
#[pyclass]
impl RustStruct {
// 定义Python可访问的属性getter
#[pygetset]
fn numbers(&self) -> NumVec {
self.numbers.clone()
}
// 定义Python可调用的方法
#[pymethod]
fn print_in_rust_from_python(&self) {
println!("Calling a rust method from python");
}
}
3.2 结构体在Python中的使用
在Python中,可以像使用普通Python类一样使用Rust定义的结构体:
# 调用Rust函数获取结构体实例
result = rust_function(42, "This is a python string", person)
# 访问结构体属性(通过#[pygetset]定义)
print("Received numbers from Rust:", result.numbers) # 输出 [1, 2, 3, 4]
# 调用结构体方法(通过#[pymethod]定义)
result.print_in_rust_from_python() # 输出 "Calling a rust method from python"
3.3 类型转换的性能考量
自定义类型转换的实现直接影响跨语言调用的性能。以下是几种常见数据结构的转换性能对比:
| 数据类型 | 转换耗时 (ns) | 内存开销 | 适用场景 |
|---|---|---|---|
| 基本类型 (i32, f64) | ~10 | 低 | 简单参数传递 |
| 字符串 (String) | ~100-500 | 中 | 短文本传递 |
| 数组/列表 | O(n) | 中 | 数据集合传递 |
| 复杂结构体 | O(n*m) | 高 | 多字段对象传递 |
优化建议:
- 对于大型数据集合,考虑使用零拷贝技术
- 避免在性能关键路径上进行频繁的类型转换
- 复杂结构体可考虑使用Protocol Buffers等序列化格式
四、方式三:双向通信 - Rust调用Python回调函数
4.1 Rust中调用Python函数的实现
RustPython允许Rust代码调用Python函数,实现双向通信。以下是在Rust中调用Python函数的示例:
// examples/call_between_rust_and_python.rs (main函数部分)
pub fn main() {
// 创建Python解释器实例
let interp = rustpython::InterpreterConfig::new()
.init_stdlib()
.init_hook(Box::new(|vm| {
// 注册Rust原生模块
vm.add_native_module(
"rust_py_module".to_owned(),
Box::new(rust_py_module::make_module),
);
}))
.interpreter();
// 进入解释器上下文
interp.enter(|vm| {
// 添加Python模块搜索路径
vm.insert_sys_path(vm.new_pyobj("examples"))
.expect("add path");
// 导入Python模块
let module = vm.import("call_between_rust_and_python", 0).unwrap();
// 调用Python回调函数
let init_fn = module.get_attr("python_callback", vm).unwrap();
init_fn.call((), vm).unwrap(); // 调用无参Python函数
// 调用带参数的Python函数
let take_string_fn = module.get_attr("take_string", vm).unwrap();
take_string_fn
.call((String::from("Rust string sent to python"),), vm)
.unwrap(); // 传递字符串参数
})
}
4.2 Python回调函数的定义
在Python中定义供Rust调用的函数:
def python_callback():
"""由Rust调用的Python回调函数"""
print("Python callback invoked from Rust!")
def take_string(string):
"""接收Rust传递的字符串参数"""
print("Python function received:", string)
4.3 双向调用的应用场景
双向调用特别适合以下场景:
- 事件驱动系统:Rust处理底层事件,Python实现高层业务逻辑
- 插件架构:主程序用Rust编写,插件用Python实现
- 科学计算:Rust实现高性能计算内核,Python提供交互界面
五、三种调用方式的对比与最佳实践
5.1 功能与性能对比
| 调用方式 | 实现复杂度 | 性能开销 | 类型支持 | 适用场景 |
|---|---|---|---|---|
| 基本类型传递 | 低 | 低 (~10ns) | 基本类型 | 简单参数传递 |
| 复杂结构体传递 | 中 | 中 (~1-10μs) | 自定义类型 | 数据封装与操作 |
| 双向回调 | 高 | 高 (~10-100μs) | 函数类型 | 事件处理、插件系统 |
5.2 错误处理最佳实践
跨语言调用中的错误处理需要特别注意:
- 使用PyResult处理异常:Rust函数应返回
PyResult<T>类型,便于Python捕获异常 - 类型检查:在类型转换时进行严格的类型检查
- 错误信息传递:确保Rust中的错误信息能正确传递到Python
// 错误处理示例
#[pyfunction]
fn safe_rust_function(value: i32, vm: &VirtualMachine) -> PyResult<()> {
if value < 0 {
// 返回Python可捕获的异常
return Err(vm.new_value_error("Value must be non-negative".to_string()));
}
Ok(())
}
在Python中捕获Rust抛出的异常:
try:
safe_rust_function(-1)
except ValueError as e:
print("Caught Rust error:", e) # 输出 "Value must be non-negative"
5.3 性能优化策略
- 减少跨语言调用次数:将多次小调用合并为一次大调用
- 使用批量处理:对大型数据集采用批量处理而非逐个元素处理
- 避免不必要的类型转换:设计数据结构时考虑跨语言传输效率
- 释放全局解释器锁(GIL):在长时间运行的Rust函数中释放GIL
// 释放GIL示例
#[pyfunction]
fn long_running_task(vm: &VirtualMachine) -> PyResult<()> {
// 释放GIL,允许Python其他线程运行
let _gil_guard = vm.release_gil();
// 执行长时间运行的任务
heavy_computation();
Ok(())
}
六、高级应用:构建混合语言应用
6.1 嵌入RustPython到Rust应用
以下是将RustPython解释器嵌入到Rust应用的完整流程:
// 完整的Rust主程序示例
use rustpython::vm::{PyObject, PyPayload, PyResult, VirtualMachine, pyclass, pymodule};
pub fn main() {
// 1. 配置并创建Python解释器
let interp = rustpython::InterpreterConfig::new()
.init_stdlib() // 初始化标准库
.init_hook(Box::new(|vm| {
// 2. 注册Rust原生模块
vm.add_native_module(
"rust_py_module".to_owned(),
Box::new(rust_py_module::make_module),
);
}))
.interpreter();
// 3. 进入解释器上下文
interp.enter(|vm| {
// 添加Python模块搜索路径
vm.insert_sys_path(vm.new_pyobj("examples"))
.expect("Failed to add path to sys");
// 4. 导入Python模块
let module = vm.import("call_between_rust_and_python", 0)
.expect("Failed to import Python module");
// 5. 调用Python函数(回调)
let init_fn = module.get_attr("python_callback", vm)
.expect("Failed to get python_callback function");
init_fn.call((), vm)
.expect("Failed to call python_callback");
// 6. 调用带参数的Python函数
let take_string_fn = module.get_attr("take_string", vm)
.expect("Failed to get take_string function");
take_string_fn
.call((String::from("Rust string sent to python"),), vm)
.expect("Failed to call take_string");
})
}
6.2 构建独立可执行文件
使用RustPython的freeze功能,可以将Python代码冻结到Rust二进制文件中,构建完全独立的可执行文件:
# 创建冻结的Python模块
cargo run --bin rpython freeze --output frozen_stdlib examples/call_between_rust_and_python.py
# 构建独立可执行文件
cargo build --release --features freeze-stdlib
6.3 实际项目案例分析
案例:科学计算应用
- 架构:Rust实现高性能线性代数库,Python提供交互界面
- 调用方式:复杂结构体传递 + 双向回调
- 性能提升:相比纯Python实现,矩阵运算性能提升约20-50倍
关键技术点:
- 使用
ndarray库进行Rust端数值计算 - 通过自定义类型转换实现
ndarray::Array与numpy.ndarray的高效转换 - 采用双向回调实现进度报告和中断机制
七、总结与展望
7.1 核心知识点回顾
本文介绍了使用RustPython实现Python调用Rust函数的三种方式:
- 基本类型传递:适用于简单参数和返回值的场景,实现简单,性能最优
- 复杂结构体传递:适合需要封装多个相关值的场景,通过宏系统简化实现
- 双向回调:适用于事件驱动和插件架构,实现Rust与Python的双向通信
7.2 RustPython的发展前景
RustPython作为一个新兴的Python解释器,正在快速发展中。未来可能的改进方向包括:
- JIT编译支持,进一步提升Python代码执行性能
- 完善C扩展兼容性,支持更多Python库
- 优化类型转换性能,减少跨语言调用开销
7.3 后续学习资源
- 官方文档:RustPython GitHub仓库中的DEVELOPMENT.md
- 示例项目:仓库中example_projects目录下的示例
- 社区支持:RustPython Discord社区和GitHub讨论区
希望本文能帮助你掌握RustPython与Rust代码互调的核心技术。如果你有任何问题或发现文中错误,欢迎在评论区留言讨论。若觉得本文对你有帮助,请点赞、收藏并关注,以便获取更多RustPython进阶教程!
下一篇预告:《RustPython性能优化实战:从字节码到机器码》
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



