专业编程基础技术教程

网站首页 > 基础教程 正文

如何在你的项目中混合 Rust 和 Python

ccvgpt 2024-12-13 12:09:28 基础教程 6 ℃

在在 2021 年Stack Overflow 年度开发者调查[1] 中,Rust 被评为“最受欢迎的语言”(连续第六年!),Python 被评为“最受欢迎”的语言。那么为什么不混合两种语言呢!本次介绍将建立一个结合 Python 和 Rust 代码的混合项目。

为什么?Python 解释方法和动态类型加速了我们的开发工作流程。但是对于更复杂的工作,我们经常会遇到性能问题。

如何在你的项目中混合 Rust 和 Python

Rust 是实现 Python 项目本机性能升级的理想选择。本机优化使用低级语言和编译器绕过 Python 解释器。

通过结合 Rust 和 Python,您可以获得两全其美:使用 Python 的快速交互式开发环境和使用 Rust 的本机性能。

Python + Rust 项目 — 作者的图片

为什么在同一个项目中使用两种语言?

在以下部分中,我将设置一个混合 Python/Rust 项目的示例。我们的项目将在同一个项目和工件中结合 Python 代码和 Rust。

混合项目的好处是显而易见的:您在同一个存储库中共享您的 Rust 和 Python 代码并创建一个工件。结果,您将两种语言结合在同一个项目中;使用原生 Rust 组件迭代和扩展 Python 代码变得更加容易。此外,只需部署一个工件,就可以更轻松地维护和共享您的项目。

缺点很明显:两种语言在同一个目录下,如何构建、测试和部署这样的项目?

如何?PyO3 + 成熟素

集成 Rust 和 Python 的主要方式是PyO3 框架。使用 PyO3,我们可以用 Rust 编写原生 Python 模块。当然,该框架也支持从 Rust 调用 Python,但我将只关注使用原生 Rust 模块扩展 Python。

PyO3 将您的 Rust 代码包装到本机 python 模块中。因此,绑定生成很容易且完全透明。

棘手的部分是构建混合 Python 和本机代码项目(Python + [Rust、C 或 C++])时代码的打包。那么如何制作集成 Python 和原生代码的轮子呢?

Python代码无需编译即可分发,与平台无关;安装轮子会.pyc即时创建文件(Python 字节码)。但是我们的 Rust 代码需要编译和分发为共享库(二进制代码)。

打包是这个项目的难点:创建一种通用的、多平台的方式来生成混合 Python-Rust 包。

可以帮助您实现此目的的工具是Maturin。Maturin 管理创建、构建、打包和分发混合 Python/Rust 项目。

PyO3 + Maturin — 图片作者

使用 Maturin 初始化一个混合项目

首先,使用pip.

$ pip 安装成熟

它包括maturin二进制文件,一个命令行界面。

$ maturin --help maturin 0.12.9
使用 pyo3、rust-cpython 和 cffi 绑定以及 rust 二进制文件作为
python 包构建和发布 crates用法:
    maturin <SUBCOMMAND>选项:
    -h,--help 打印帮助信息
    -V, --version 打印版本信息SUBCOMMANDS: 
    build 将 crate 构建到 python 包
    中 develop 将 crate 作为模块安装在当前 virtualenv 
    help 打印此消息或给定子命令的帮助
    init 在现有目录
    列表中创建一个新的货物项目-python 搜索并列出可用的 python 安装
    new 创建一个新的 cargo 项目
    publish 构建并将 crate 作为 python 包发布到 pypi 
    sdist 只构建一个源分发(sdist)而不编译
    上传 将 python 包上传到 pypi

可执行文件提出了maturin几个选项。让我们放大构建开发

  • build:编译 Rust 并将其与 Python 包集成。它生成一个 Wheel 包,将 Rust 生成的二进制工件与最终的 Python 代码混合在一起。
  • develop:在开发和调试项目时很有用。此命令直接在 Python 模块中构建和安装新创建的共享库。

为了测试一个混合的 Rust-Python 项目,我们现在可以使用maturin new命令初始化我们的库。

$ maturin new --help maturin-new
创建一个新的货物项目用法:
    maturin new [OPTIONS] <PATH> ARGS: 
    <PATH> 项目路径OPTIONS: 
    -b, --bindings <BINDINGS> 使用哪种绑定 [可能values: pyo3, rust-cpython, 
                                 cffi, bin] 
    -h, --help 打印帮助信息
        --mixed 使用混合Rust/Python项目布局
        --name <NAME> 设置生成的包名,默认为目录名

对于我们的项目,我们使用选项--bindings pyo3和--mixed my_project. 该my_project参数与此示例的目标项目目录匹配。

$ maturin new --bindings pyo3 --mixed my_project

生成的项目将目录中的 Python 包my_project与 Rust 项目定义Cargo.toml和基于 Rust 的src目录集成在一起。

$ tree my_project my_project 
├── Cargo.toml 
├── my_project 
│ └── __init__.py 
├── pyproject.toml 
├── src 
│ └── lib.rs 
└── test 
    └── test.py 3个目录, 5 个文件

好的,我们有了基本的项目骨架。我们现在可以添加一个简单的 Rust 函数来公开。

项目结构 — 作者图片

用 PyO3 包装 Python 代码

我们需要在 Python 运行时可调用的 Python 模块中声明和导出 Rust 函数。我们首先使用#[pymodule]Rust 宏创建一个模块。在我们的函数中,my_project我们将声明 Rust 和 Python 之间的绑定。

→ 锈?my_project/src/lib.rs? =使用pyo3::prelude::*; (1) (2)
 <<functions>>#[pymodule]
 fn my_project(_py: Python , m: & PyModule ) -> PyResult <()> {
     (3)
     <<function_declarations>>
    Ok(())
}   ( 4) <<测试>>
  • (1)我们包括Py03定义和宏。
  • (2)在<<functions>>块中,我们声明了我们的 Rust 函数。
  • (3)在<<function_declarations>>块中,我们在最终的 Python 模块中公开我们的 Rust 函数。
  • (4)在<<tests>>块中,我们添加了 Rust 单元测试函数。

一个简单的函数

让我们从<<functions>>块的简单函数开始。我们的导出函数is_prime通过将其除以前面的数字来检查其输入的素数。对于数字num,我们检查 2 和 之间数字的除法余数√num。

→ rust ?functions? = #[pyfunction] (1) 
fn is_prime(num: u32) -> bool {
     match num {
        0 | 1 => false ,
        _ => {
             let limit = (num as f32).sqrt() as u32 ; (2)             (2..=limit).any(|i| num % i == 0) == false  (3)
         }
    }
}
  • (1) Rust 宏#[pyfunction]为 Python 绑定生成代码。
  • (2)计算我们的试除数系列的上限。
  • (3)生成试验并测试除法的其余部分。2..=limit生成介于2和limit(包括)之间的范围。any检查生成的元素之一是否满足谓词。

在<<function_declarations>>块中,我们将函数添加到导出的模块中。

→ 锈?function_declarations? = m.add_function(wrap_pyfunction!(is_prime, m)?)?;

在该<<tests>>块中,我们添加了一些简单的单元测试。

→ rust ?tests? = #[cfg(test)]
 mod 测试{
    使用 super ::*;     #[测试]
     fn simple_test_false() {
        assert_eq!(is_prime(0), false ); 
        assert_eq!(is_prime(1), false ); 
        assert_eq!(is_prime(12), false )
    }    #[test]
     fn simple_test_true() {
        assert_eq!(is_prime(2), true ); 
        assert_eq!(is_prime(3), true ); 
        assert_eq!(is_prime(41), true )
    }
}

构建并运行您的 Python 模块

使用 Maturin,构建一个原生 Rust 模块并将其导出到 Python 解释器中只需一行代码。

$ cd my_project 
$ maturin 开发

该命令构建 Rust 原生模块并将其部署在当前的 virtualenv 中。

导入 我的项目打印(我的项目.is_prime (12))
打印(我的项目.is_prime (11))>假
>真

在引擎盖后面,命令maturin develop:

  • 使用 Cargo 编译原生 Rust 模块:在本地 python 模块中编译和复制共享库。
  • 安装 Python 模块:该模块安装在您的 virtualenv 中。对于无需在每次 Python 代码更改时重新构建项目的即时测试,您可以通过在( Maturin Editable Installs )中添加以下行来使用可编辑安装(ie )。pip install -epyproject.yml
[构建系统]
需要 = [ "maturin>=0.12" ]
构建后端 = "maturin"

测试你的模块

我们可以在 Python 中添加一个简单的基于属性的测试来检查is_primeRust 函数的行为。

我们首先为Hypothesis Python 包添加一个依赖项。要将依赖项添加到我们的项目中,我们可以编辑该pyproject.toml文件。Maturin 支持PEP 621,启用 Python 元数据规范。

→ toml ?my_project/pyproject.toml? = [build-system]
 requires = [ "maturin>=0.12" ]
build-backend = "maturin" [project.optional-dependencies]
 test = [
   "hypothesis" ,
   "sympy"
 ]   [project] name = " my_project "
 requires-python = ">=3.6"
分类器 = [
     "Programming Language :: Rust" ,
     "Programming Language :: Python :: Implementation :: CPython" ,
]

我们现在可以运行maturin develop命令了。

$ cd my_project 
$ maturin 开发 --extras 测试(1)

(1)使用--extras testMaturin 选项安装 Python 测试依赖项。

我们添加了一个简单的基于属性的测试。

→ Python ?my_project/test/test.py? =从 假设 导入设置,详细程度,
从 假设 导入策略作为st
 from  sympy.ntheory  import isprime
 import  my_project @given(s=st.integers(min_value=1, max_value=2 **10))
@settings(verbosity=Verbosity.normal, max_examples=500)
 def test_is_prime(s):
     assert isprime(s) == my_project.is_prime(s)   if __name__ == "__main__" :
       test_is_prime()

我们终于可以运行基于属性的 Python 测试了。

$ cd my_project 
$ python test/test.py

还有我们的 Rust 测试。

$ cd my_project 
$ 货物测试

Tags:

最近发表
标签列表