简介
PyO3 是一个 Rust 库,它提供了 Rust 和 Python 之间的双向绑定。通过 PyO3,你可以:
- 在 Python 中调用 Rust 函数和类
- 在 Rust 中调用 Python 代码
- 用 Rust 编写 Python 扩展模块
PyO3 的主要优势包括:
- 性能:Rust 的执行速度通常比 Python 快得多,特别是在计算密集型任务中
- 安全性:Rust 的内存安全保证可以避免许多常见的错误
- 并发:利用 Rust 的并发特性,同时保持与 Python 的兼容性
- 生态系统:结合 Python 丰富的库生态系统和 Rust 的性能优势
安装与配置
前提条件
- Rust(推荐使用 rustup 安装)
- Python 3.7+
- 适当的开发工具(如 gcc、Visual Studio 等,取决于你的平台)
创建新项目
- 创建一个新的 Rust 库项目:
bash
cargo new --lib my_python_module
cd my_python_module
- 在
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"] }
- 安装
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> |
None 或 T 的 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)
}
自定义类型
对于自定义类型,你可以实现 FromPyObject
和 IntoPy
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]
最佳实践
-
保持简单接口:尽量使 Rust 和 Python 之间的接口简单明了,避免复杂的类型转换
-
合理使用 GIL :对于计算密集型任务,考虑使用
py.allow_threads()
释放 GIL -
错误处理 :始终返回
PyResult<T>
并提供有意义的错误信息 -
文档:为你的函数和类添加文档字符串,这些会被转换为 Python 的文档字符串
-
测试:同时编写 Rust 和 Python 测试来确保你的绑定正常工作
-
性能优化:将性能关键部分移至 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 官方文档 - 完整的API文档和使用指南
- PyO3 GitHub 仓库 - 源代码和问题跟踪
- PyO3 用户指南 - 详细的使用教程
- PyO3 API 参考 - 详细的API文档
构建工具
- maturin - 用于构建和发布PyO3模块的工具
- setuptools-rust - Setuptools的Rust扩展
示例和教程
- PyO3 示例仓库 - 官方示例代码
- Rust for Python开发者 - Red Hat开发者博客
- 使用PyO3加速Python - 实用教程
相关技术
- Rust 编程语言官网 - Rust语言官方网站
- Python 官网 - Python语言官方网站
- CFFI - Python的C外部函数接口
- ctypes - Python的外部函数库
这个教程提供了 PyO3 的基础知识和一些高级用法,希望能帮助你开始使用 PyO3 构建高性能的 Python 扩展。随着你对 PyO3 的深入了解,你将能够充分利用 Rust 的性能和安全性,同时保持 Python 的易用性和丰富的生态系统,写作不易,点个赞收藏一下吧~。