当前位置: 首页 > news >正文

从零到一 —— 使用 PyO3 将 Rust 函数编译为 Python 模块

1. 引言:消除隔阂,一步调用

在上一篇中,我们分别用 Python 和 Rust 写了两个独立的 HTTP 服务器。虽然对比了性能,但它们本质上是两个独立的程序。

在实际开发中,我们往往不想维护两套服务。我们希望的结果是:Python 负责主流程和业务逻辑,而在性能瓶颈处,直接调用 Rust 编写的函数,就像调用普通的 Python 库一样自然。

这就需要一个桥梁。PyO3​ 就是这个桥梁,它允许你将 Rust 代码编译成一个.so(Linux/Mac)或.pyd(Windows)的二进制模块。Python 通过import加载它,中间的转换开销极小。

今天,我们就把上一篇中的count_words函数,打包成一个名为rust_analyzer的 Python 扩展库。

2. 环境准备:Maturin 工具链

为了编译 Rust 生成的 Python 模块,我们需要特定的工具。maturin是 PyO3 官方推荐的工具,它极大地简化了配置过程。

2.1 创建虚拟环境

为了保持环境整洁,我们先创建一个 Python 虚拟环境:

# 创建项目文件夹 mkdir pyo3_demo && cd pyo3_demo # 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate

2.2 安装 Maturin

pip install maturin

3. 初始化 PyO3 项目

maturin提供了一个命令,可以快速生成符合 PyO3 规范的项目骨架。

# 在当前目录下初始化一个名为 rust_analyzer 的库 maturin init --bindings pyo3

运行后,它会询问你选择哪种绑定方式,直接回车选择默认的pyo3即可。

生成的项目结构如下:

pyo3_demo/ ├── Cargo.toml # Rust 项目管理文件 ├── pyproject.toml # Python 构建配置文件 └── src/ └── lib.rs # Rust 源代码(主要编辑这里)

4. 编写 Rust 代码:导出函数

打开src/lib.rs,清空默认内容,填入以下代码。这段代码的核心任务是:告诉 Rust 如何将函数暴露给 Python

文件路径:src/lib.rs

/* * 使用 PyO3 编写 Python 扩展模块 * 目标:将上一篇的 Rust 词频统计逻辑暴露给 Python */ // 引入 PyO3 预导入模块,包含所有必要的宏和类型 use pyo3::prelude::*; use std::collections::HashMap; /// 核心逻辑:纯粹的 Rust 函数 /// 这个函数不关心 Python,只负责高效的词频计算 /// 输入: 文本字符串和需要返回的 Top N /// 输出: 元组数组 (单词, 次数) fn count_words_rust(text: &str, top_n: usize) -> Vec<(String, u64)> { let mut freq_map = HashMap::new(); // 使用 Rust 高效的迭代器进行分词和统计 text.split(|c: char| !c.is_alphanumeric()) .filter(|s| !s.is_empty()) .for_each(|word| { *freq_map.entry(word.to_lowercase()).or_insert(0) += 1; }); // 排序并截取前 N 名 let mut freq_vec: Vec<_> = freq_map.into_iter().collect(); freq_vec.sort_by(|a, b| b.1.cmp(&a.1)); freq_vec.truncate(top_n); freq_vec } /// Python 接口函数 /// #[pyfunction] 宏:标记此函数为 Python 可调用函数 /// 参数类型会自动转换:Python str -> &str, Python int -> usize /// 返回值会自动转换:Vec<(String, u64)> -> Python list of tuples #[pyfunction] fn count_words(text: &str, top_n: usize) -> PyResult<Vec<(String, u64)>> { // 调用 Rust 核心逻辑 let result = count_words_rust(text, top_n); // Ok(...) 是 PyO3 的标准返回方式,用于处理 Python 异常 Ok(result) } /// 定义 Python 模块 /// #[pymodule] 宏:定义模块名称为 rust_analyzer /// _py: Python 解释器的句柄(GIL Token) /// m: 模块对象本身 #[pymodule] fn rust_analyzer(_py: Python<'_>, m: &PyModule) -> PyResult<()> { // 将 count_words 函数注册到模块中 m.add_function(wrap_pyfunction!(count_words, m)?)?; Ok(()) }

关键点解析(博客重点):

  1. 分离逻辑count_words_rust是纯逻辑,count_words是接口层。这样做的好处是 Rust 代码可以不依赖 Python 环境进行单元测试。

  2. #[pyfunction]:这是魔法发生的地方,它让 Rust 函数能被import进来。

  3. PyResult:Rust 的错误处理与 Python 的异常系统对接。

5. 编译与安装(开发模式)

这是最关键的一步。我们需要将 Rust 代码编译成二进制文件,并链接到当前的 Python 虚拟环境中。

在项目根目录(pyo3_demo/)下执行:

maturin develop

发生了什么?

  1. cargo开始编译 Rust 代码。

  2. 生成一个二进制文件(如rust_analyzer.cpython-310-x86_64-linux-gnu.so)。

  3. maturin将这个文件复制到当前虚拟环境的site-packages目录下。

看到类似📦 Built wheel for CPython 3.10✏️ Installed rust_analyzer的字样,就意味着成功了!

6. 在 Python 中调用

现在,激动人心的时刻到了。我们可以在 Python 中像使用普通库一样使用刚才编译的 Rust 模块。

在项目根目录下创建一个测试文件test.py

文件路径:test.py

# 直接导入我们刚刚用 Rust 编译的模块 import rust_analyzer def test_rust_function(): print("--- Testing Rust Module ---") # 准备测试数据 text = "Rust is fast and safe. Python is flexible and easy. Rust and Python together are powerful." top_n = 3 # 直接调用 Rust 函数 # PyO3 自动帮我们把 Python str 转成了 Rust &str # 并把 Rust 返回的 Vec<(String, u64)> 转成了 Python list result = rust_analyzer.count_words(text, top_n) print(f"Input Text: {text}") print(f"Top {top_n} words:") for word, count in result: print(f" {word}: {count}") if __name__ == "__main__": test_rust_function()

运行测试:

python test.py

预期输出:

--- Testing Rust Module --- Input Text: Rust is fast and safe. Python is flexible and easy. Rust and Python together are powerful. Top 3 words: rust: 2 python: 2 and: 2

7. 流程总结与 Mermaid 图示

整个流程可以简化为以下几个步骤:

8. 总结

至此,我们成功打通了 Rust 到 Python 的调用链路。

  • 没有 HTTP 开销:函数调用发生在进程内部,速度极快。

  • 开发体验流畅maturin develop支持增量编译,修改 Rust 代码后重新运行该命令即可更新模块。

  • 类型安全:Rust 的强类型系统在编译期拦截了大量错误,Python 端拿到的数据是安全的。

http://www.cnnetsun.cn/news/3269510.html

相关文章:

  • 高效实战:全面解析VMPDump的VMProtect 3.X x64保护破解技巧
  • STM32与AD7175-8高精度数据采集系统设计指南
  • Edyn物理引擎调试与优化:常见问题解决方案与性能调优
  • Palworld Host Save Fix:终极指南 - 解决服务器迁移中的角色创建问题
  • 服务端收到一个注册请求,从 Controller 到内存 Map 走了 5 层:服务注册模块源码走读
  • 终极网页浏览效率神器:右键滚动条一键返回顶部
  • MultiMarkdown-6:从轻量标记到专业出版的文档处理引擎
  • ADP5350与dsPIC33FJ电源管理方案设计指南
  • AMD Ryzen调试工具终极指南:SMU Debug Tool完整使用教程
  • 基于PIC32与压电蜂鸣器的宽温警报系统设计
  • 三步搞定!用podcast-dl轻松建立你的个人播客收藏库
  • 高电压DC-DC升压转换系统设计与实现
  • STM32与ADS1015L嵌入式信号采集系统设计与优化
  • emmet-mode高级技巧:嵌套、乘法与编号表达式实战
  • CodeContracts架构解析:.NET契约式设计的深度优化与实战指南
  • 如何解决Atmosphere系统模块冲突导致的启动失败问题
  • STM32与TLP241A实现工业级电气隔离设计
  • 5个高效方法:用Claude Code提升你的AI编程工作流
  • 2026年口碑好的聚合API接口优质服务商,究竟藏着哪些服务秘诀?
  • Lens终极指南:5个Kubernetes集群可视化技巧让你秒变运维专家
  • Midscene.js架构解析:视觉AI驱动的跨平台自动化测试革命
  • 零代码AI自动化神器:Midscene.js让你的跨平台UI测试变得如此简单
  • AMDVLK深度解析:Radeon显卡Vulkan驱动的开源架构与技术实现
  • 当技术梦想遭遇法律边界:一个开源项目的合规启示录
  • hoverboard-firmware-hack-FOC:从传统换相到场定向控制的技术演进
  • Neko漫画阅读器终极指南:如何轻松整合Toonily与Weeb Central等多平台资源
  • 基于MAX77654与STM32的智能电源管理系统设计
  • OAuth2.0 与 JWT 深度整合实战:CAS 6.6 配置 JWT 作为访问令牌
  • 阿里云发布两款 AI 核心产品,解决企业 AI 部署难题,构建全生命周期治理闭环
  • LevelDB数据透视镜:dumpfile工具完全指南