PyO3 教程:连接 Python 与 Rust 的桥梁

简介

PyO3 是一个 Rust 库,它提供了 Rust 和 Python 之间的双向绑定。通过 PyO3,你可以:

  • 在 Python 中调用 Rust 函数和类
  • 在 Rust 中调用 Python 代码
  • 用 Rust 编写 Python 扩展模块

PyO3 的主要优势包括:

  1. 性能:Rust 的执行速度通常比 Python 快得多,特别是在计算密集型任务中
  2. 安全性:Rust 的内存安全保证可以避免许多常见的错误
  3. 并发:利用 Rust 的并发特性,同时保持与 Python 的兼容性
  4. 生态系统:结合 Python 丰富的库生态系统和 Rust 的性能优势

安装与配置

前提条件

  • Rust(推荐使用 rustup 安装)
  • Python 3.7+
  • 适当的开发工具(如 gcc、Visual Studio 等,取决于你的平台)

创建新项目

  1. 创建一个新的 Rust 库项目:
bash 复制代码
cargo new --lib my_python_module
cd my_python_module
  1. Cargo.toml 中添加 PyO3 依赖:
toml 复制代码
[package]
name = "my_python_module"
version = "0.1.0"
edition = "2021"

[lib]
name = "my_python_module"
# "cdylib" 用于创建可以被 Python 导入的动态库
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.20.0", features = ["extension-module"] }
  1. 安装 maturin,这是一个用于构建和发布 PyO3 模块的工具:
bash 复制代码
pip install maturin

基础用法

创建 Python 模块

src/lib.rs 中,你可以定义一个 Python 模块:

rust 复制代码
use pyo3::prelude::*;

/// 这个模块是用 Rust 实现的 Python 模块
#[pymodule]
fn my_python_module(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    m.add_class::<MyClass>()?;
    Ok(())
}

导出函数到 Python

使用 #[pyfunction] 装饰器可以将 Rust 函数导出到 Python:

rust 复制代码
/// 计算两个数的和并返回字符串
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

在 Python 中使用:

python 复制代码
import my_python_module

result = my_python_module.sum_as_string(5, 7)  # 返回 "12"
print(result)  # 输出: 12

导出类到 Python

使用 #[pyclass]#[pymethods] 装饰器可以将 Rust 结构体导出为 Python 类:

rust 复制代码
#[pyclass]
struct MyClass {
    #[pyo3(get, set)]
    value: i32,
    internal_value: String,
}

#[pymethods]
impl MyClass {
    #[new]
    fn new(value: i32) -> Self {
        MyClass {
            value,
            internal_value: String::from("内部值"),
        }
    }

    fn get_internal_value(&self) -> PyResult<String> {
        Ok(self.internal_value.clone())
    }

    fn double_value(&mut self) -> PyResult<i32> {
        self.value *= 2;
        Ok(self.value)
    }

    #[staticmethod]
    fn static_method(value: i32) -> PyResult<i32> {
        Ok(value * 2)
    }

    #[classmethod]
    fn class_method(_cls: &PyType, value: i32) -> PyResult<i32> {
        Ok(value * 3)
    }
}

在 Python 中使用:

python 复制代码
from my_python_module import MyClass

# 创建实例
obj = MyClass(10)

# 访问属性
print(obj.value)  # 输出: 10

# 修改属性
obj.value = 20
print(obj.value)  # 输出: 20

# 调用方法
print(obj.get_internal_value())  # 输出: 内部值
print(obj.double_value())  # 输出: 40

# 调用静态方法
print(MyClass.static_method(5))  # 输出: 10

# 调用类方法
print(MyClass.class_method(5))  # 输出: 15

类型转换

基本类型

PyO3 自动处理 Rust 和 Python 之间的基本类型转换:

Rust 类型 Python 类型
bool bool
i8, i16, i32, i64 int
u8, u16, u32, u64 int
f32, f64 float
&str, String str
() None
Option<T> NoneT 的 Python 等价物
Result<T, E> T 的 Python 等价物或引发异常

容器类型

对于容器类型,PyO3 提供了转换机制:

rust 复制代码
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};

#[pyfunction]
fn create_list(py: Python) -> PyResult<&PyList> {
    let list = PyList::new(py, &[1, 2, 3, 4]);
    Ok(list)
}

#[pyfunction]
fn create_dict(py: Python) -> PyResult<&PyDict> {
    let dict = PyDict::new(py);
    dict.set_item("key1", "value1")?;
    dict.set_item("key2", 42)?;
    Ok(dict)
}

#[pyfunction]
fn process_list(py: Python, list: &PyList) -> PyResult<usize> {
    let mut sum = 0;
    for item in list.iter() {
        sum += item.extract::<usize>()?;
    }
    Ok(sum)
}

自定义类型

对于自定义类型,你可以实现 FromPyObjectIntoPy trait:

rust 复制代码
use pyo3::prelude::*;

#[derive(Debug)]
struct Point {
    x: f64,
    y: f64,
}

impl<'source> FromPyObject<'source> for Point {
    fn extract(ob: &'source PyAny) -> PyResult<Self> {
        let x = ob.getattr("x")?.extract::<f64>()?;
        let y = ob.getattr("y")?.extract::<f64>()?;
        Ok(Point { x, y })
    }
}

impl IntoPy<PyObject> for Point {
    fn into_py(self, py: Python) -> PyObject {
        let dict = PyDict::new(py);
        dict.set_item("x", self.x).unwrap();
        dict.set_item("y", self.y).unwrap();
        dict.into()
    }
}

#[pyfunction]
fn distance(point: Point) -> PyResult<f64> {
    Ok((point.x.powi(2) + point.y.powi(2)).sqrt())
}

错误处理

PyO3 使用 PyResult<T> 类型来处理错误,这是 Result<T, PyErr> 的类型别名:

rust 复制代码
use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;

#[pyfunction]
fn divide(a: i32, b: i32) -> PyResult<i32> {
    if b == 0 {
        return Err(PyValueError::new_err("除数不能为零"));
    }
    Ok(a / b)
}

你也可以创建自定义异常类型:

rust 复制代码
use pyo3::prelude::*;
use pyo3::create_exception;

create_exception!(my_module, CustomError, pyo3::exceptions::PyException);

#[pyfunction]
fn may_raise_custom_error(value: i32) -> PyResult<i32> {
    if value < 0 {
        return Err(CustomError::new_err("值不能为负数"));
    }
    Ok(value)
}

#[pymodule]
fn my_module(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(may_raise_custom_error, m)?)?;
    m.add("CustomError", py.get_type::<CustomError>())?;
    Ok(())
}

Python GIL 管理

Python 的全局解释器锁(GIL)在使用 PyO3 时需要特别注意:

rust 复制代码
use pyo3::prelude::*;

#[pyfunction]
fn gil_example(py: Python) -> PyResult<usize> {
    // 这段代码在持有 GIL 的情况下执行
    let result1 = py.allow_threads(|| {
        // 这段代码在释放 GIL 的情况下执行
        // 可以进行耗时的计算而不阻塞 Python 解释器
        let mut sum = 0;
        for i in 0..1_000_000 {
            sum += i;
        }
        sum
    });
    
    // 现在我们又持有 GIL 了
    Ok(result1)
}

高级主题

异步支持

PyO3 支持与 Python 的异步功能集成:

rust 复制代码
use pyo3::prelude::*;
use pyo3_asyncio::tokio::future_into_py;
use std::time::Duration;

#[pyfunction]
fn sleep_and_return(py: Python, seconds: f64) -> PyResult<&PyAny> {
    let fut = async move {
        tokio::time::sleep(Duration::from_secs_f64(seconds)).await;
        seconds * 2.0
    };
    future_into_py(py, fut)
}

#[pymodule]
fn async_module(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sleep_and_return, m)?)?;
    Ok(())
}

在 Python 中使用:

python 复制代码
import asyncio
from async_module import sleep_and_return

async def main():
    result = await sleep_and_return(1.5)  # 等待 1.5 秒
    print(result)  # 输出: 3.0

asyncio.run(main())

内存管理

PyO3 使用引用计数来管理内存,与 Python 的内存管理模型一致:

rust 复制代码
use pyo3::prelude::*;

#[pyfunction]
fn memory_example(py: Python) -> PyResult<()> {
    let locals = PyDict::new(py);
    
    // 创建一个 Python 对象
    locals.set_item("x", vec![1, 2, 3])?;
    
    // 执行 Python 代码
    py.run("assert x == [1, 2, 3]", None, Some(locals))?;
    
    Ok(())
}

回调函数

PyO3 允许将 Python 函数作为回调传递给 Rust 函数:

rust 复制代码
use pyo3::prelude::*;
use pyo3::types::PyFunction;

#[pyfunction]
fn call_with_callback(py: Python, callback: &PyFunction, value: i32) -> PyResult<i32> {
    // 调用 Python 函数
    let result = callback.call1((value,))?;
    result.extract::<i32>()
}

#[pyfunction]
fn process_items(py: Python, items: Vec<i32>, callback: &PyFunction) -> PyResult<Vec<i32>> {
    let mut results = Vec::new();
    for item in items {
        let result = callback.call1((item,))?.extract::<i32>()?;
        results.push(result);
    }
    Ok(results)
}

在 Python 中使用:

python 复制代码
from my_module import call_with_callback, process_items

def double(x):
    return x * 2

result = call_with_callback(double, 5)  # 返回 10
print(result)  # 输出: 10

results = process_items([1, 2, 3, 4], double)  # 返回 [2, 4, 6, 8]
print(results)  # 输出: [2, 4, 6, 8]

最佳实践

  1. 保持简单接口:尽量使 Rust 和 Python 之间的接口简单明了,避免复杂的类型转换

  2. 合理使用 GIL :对于计算密集型任务,考虑使用 py.allow_threads() 释放 GIL

  3. 错误处理 :始终返回 PyResult<T> 并提供有意义的错误信息

  4. 文档:为你的函数和类添加文档字符串,这些会被转换为 Python 的文档字符串

  5. 测试:同时编写 Rust 和 Python 测试来确保你的绑定正常工作

  6. 性能优化:将性能关键部分移至 Rust,保持 Python 接口简洁

常见问题解答

Q: 如何在 Rust 中导入 Python 模块?

rust 复制代码
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;

#[pyfunction]
fn use_numpy(py: Python) -> PyResult<()> {
    let np = py.import("numpy")?;
    let array = np.call_method1("array", ([1, 2, 3],))?;
    let result = np.call_method1("sum", (array,))?;
    println!("{}", result);
    Ok(())
}

Q: 如何处理 Python 的字典、列表等复杂类型?

rust 复制代码
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};

#[pyfunction]
fn process_dict_and_list(py: Python, dict: &PyDict, list: &PyList) -> PyResult<&PyDict> {
    let result = PyDict::new(py);
    
    // 处理字典
    for (key, value) in dict.iter() {
        let key_str = key.extract::<String>()?;
        let value_int = value.extract::<i32>()?;
        result.set_item(key_str, value_int * 2)?;
    }
    
    // 处理列表
    let sum: i32 = list.iter()
        .map(|item| item.extract::<i32>().unwrap_or(0))
        .sum();
    result.set_item("sum", sum)?;
    
    Ok(result)
}

Q: 如何在 Rust 中捕获 Python 异常?

rust 复制代码
use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;

#[pyfunction]
fn catch_python_exception(py: Python) -> PyResult<()> {
    let result = py.run("1/0", None, None);
    match result {
        Ok(_) => println!("代码执行成功"),
        Err(e) => {
            println!("捕获到 Python 异常: {}", e);
            if e.is_instance_of::<pyo3::exceptions::PyZeroDivisionError>(py) {
                println!("这是一个除零错误");
            }
        }
    }
    Ok(())
}

参考资源

官方资源

构建工具

示例和教程

相关技术


这个教程提供了 PyO3 的基础知识和一些高级用法,希望能帮助你开始使用 PyO3 构建高性能的 Python 扩展。随着你对 PyO3 的深入了解,你将能够充分利用 Rust 的性能和安全性,同时保持 Python 的易用性和丰富的生态系统,写作不易,点个赞收藏一下吧~。

相关推荐
漫谈网络5 分钟前
闭包与作用域的理解
python·装饰器·闭包·legb
滴答滴答嗒嗒滴7 分钟前
Python小练习系列 Vol.5:数独求解(经典回溯 + 剪枝)
python·深度优先·剪枝
Alger_Hamlet16 分钟前
Pycharm 2024.3 Python开发工具
ide·python·pycharm
techdashen17 分钟前
性能比拼: Go(Gin) vs Python(Flask)
python·golang·gin
techdashen44 分钟前
性能比拼: Go标准库 vs Python FastAPI
python·golang·fastapi
键盘上的GG小怪兽GG1 小时前
Centos主机检查脚本
开发语言·网络·python
ak啊1 小时前
PyTorch框架-Python GPU编程
pytorch·python·gpu
Python之栈1 小时前
再见VS Code!Google IDE 正颠覆传统开发体验
ide·vscode·python
lzq6031 小时前
【Python实战】用Pandas轻松实现Excel数据清洗与可视化
python·excel·pandas
engchina2 小时前
vLLM 部署 openai whisper 模型实现语音转文字
人工智能·python·whisper