Rust与Python互操作库cpython的使用:实现Rust调用Python代码的高效桥梁

Rust与Python互操作库cpython的使用:实现Rust调用Python代码的高效桥梁

警告:这个包不再积极维护。请切换到PyO3。

这是Rust与Python解释器的绑定库。

支持的Python版本

  • Python 2.7
  • Python 3.7至3.12

需要Rust 1.41.1或更高版本。

使用方法

Cargo.toml中添加以下依赖:

[dependencies]
cpython = "0.7"

示例程序:显示sys.version的值

use cpython::{Python, PyDict, PyResult};

fn main() {
    let gil = Python::acquire_gil();
    hello(gil.python()).unwrap();
}

fn hello(py: Python) -> PyResult<()> {
    let sys = py.import("sys")?;
    let version: String = sys.get(py, "version")?.extract(py)?;

    let locals = PyDict::new(py);
    locals.set_item(py, "os", py.import("os")?)?;
    let user: String = py.eval("os.getenv('USER') or os.getenv('USERNAME')", None, Some(&locals))?.extract(py)?;

    println!("Hello {}, I'm Python {}", user, version);
    Ok(())
}

示例:带有Python绑定的库

以下两个文件可以使用cargo build构建,并生成一个Python兼容的库。在Mac OS上,需要将输出从*.dylib重命名为*.so。在Windows上,需要将输出从*.dll重命名为*.pyd。

Cargo.toml:

[lib]
name = "rust2py"
crate-type = ["cdylib"]

[dependencies.cpython]
version = "0.7"
features = ["extension-module"]

src/lib.rs

use cpython::{PyResult, Python, py_module_initializer, py_fn};

// 为生成的Python模块添加绑定
// 注意:"rust2py"必须与.so或.pyd文件名相同
py_module_initializer!(rust2py, |py, m| {
    m.add(py, "__doc__", "This module is implemented in Rust.")?;
    m.add(py, "sum_as_string", py_fn!(py, sum_as_string_py(a: i64, b:i64)))?;
    Ok(())
});

// 实现为普通Rust函数的逻辑
fn sum_as_string(a:i64, b:i64) -> String {
    format!("{}", a + b).to_string()
}

// rust-cpython感知的函数。我们所有的Python接口都可以
// 声明在一个单独的模块中。
// 注意:py_fn!()宏会自动将参数从Python对象转换为Rust值;
// 并将Rust返回值转换回Python对象。
fn sum_as_string_py(_: Python, a:i64, b:i64) -> PyResult<String> {
    let out = sum_as_string(a, b);
    Ok(out)
}

在Windows和Linux上,可以使用cargo build --release正常构建。在Mac OS上,需要设置额外的链接器参数。最简单的解决方案是创建一个.cargo/config文件,内容如下:

[target.x86_64-apple-darwin]
rustflags = [
  "-C", "link-arg=-undefined",
  "-C", "link-arg=dynamic_lookup",
]

开发

要构建这个crate,运行:make build

要测试这个crate,运行:make test

注意:这个crate有几个文件是通过脚本自动生成的。使用Makefile可以确保这些文件根据需要重新生成。

完整示例

以下是一个更完整的示例,展示如何在Rust中调用Python函数:

use cpython::{Python, PyResult, PyDict};

fn main() {
    let gil = Python::acquire_gil();
    let py = gil.python();
    
    // 导入Python模块
    let sys = py.import("sys").unwrap();
    
    // 获取Python版本
    let version: String = sys.get(py, "version").unwrap().extract(py).unwrap();
    println!("Python version: {}", version);
    
    // 调用Python函数
    let math = py.import("math").unwrap();
    let result: f64 = math.call(py, "sqrt", (25.0,), None).unwrap().extract(py).unwrap();
    println!("Square root of 25 is: {}", result);
    
    // 使用Python字典
    let dict = PyDict::new(py);
    dict.set_item(py, "key1", "value1").unwrap();
    dict.set_item(py, "key2", 42).unwrap();
    
    // 执行Python代码
    let locals = PyDict::new(py);
    locals.set_item(py, "my_dict", dict).unwrap();
    let code = "my_dict['key3'] = my_dict['key2'] * 2";
    py.run(code, None, Some(&locals)).unwrap();
    
    let key3: i32 = locals.get_item(py, "my_dict")
        .unwrap()
        .downcast_into::<PyDict>(py)
        .unwrap()
        .get_item(py, "key3")
        .unwrap()
        .extract(py)
        .unwrap();
    
    println!("key3 value: {}", key3);
}

这个示例展示了:

  1. 如何导入Python模块
  2. 如何获取Python对象的属性
  3. 如何调用Python函数
  4. 如何在Rust中创建和使用Python字典
  5. 如何执行Python代码并获取结果

注意:虽然cpython库仍然可以使用,但建议考虑使用更新的PyO3库进行Rust与Python的互操作。


1 回复

Rust与Python互操作库cpython的使用指南

介绍

cpython库是Rust与Python互操作的一个强大工具,它提供了直接从Rust代码调用Python功能的接口。这个库基于Python的C API构建,允许Rust开发者:

  • 在Rust中执行Python代码
  • 调用Python函数和模块
  • 在两种语言间转换数据类型
  • 管理Python对象的生命周期

相比其他方案,cpython提供了更底层的控制,适合需要高性能互操作的场景。

基本使用方法

添加依赖

首先在Cargo.toml中添加依赖:

[dependencies]
cpython = "0.7"

初始化Python解释器

use cpython::{Python, PyResult};

fn main() {
    // 获取Python解释器锁
    let gil = Python::acquire_gil();
    let py = gil.python();
    
    // 现在可以执行Python代码了
    let version = py.run("import sys; sys.version", None, None).unwrap();
    println!("Python version: {}", version);
}

调用Python函数

use cpython::{Python, PyResult, PyDict};

fn call_python_function(py: Python) -> PyResult<()> {
    // 导入math模块
    let math = py.import("math")?;
    
    // 调用math.sqrt函数
    let result: f64 = math.call(py, "sqrt", (16.0,), None)?.extract(py)?;
    println!("Square root of 16 is {}", result);
    
    Ok(())
}

传递复杂参数

use cpython::{Python, PyResult, PyDict};

fn call_with_kwargs(py: Python) -> PyResult<()> {
    let random = py.import("random")?;
    
    // 创建关键字参数字典
    let kwargs = PyDict::new(py);
    kwargs.set_item(py, "a", 1)?;
    kwargs.set_item(py, "b", 10)?;
    
    // 调用random.randint(a=1, b=10)
    let result: i32 = random.call(py, "randint", (1, 10), Some(&kwargs))?.extract(py)?;
    println!("Random number: {}", result);
    
    Ok(())
}

高级用法

在Rust中定义Python可调用对象

use cpython::{Python, PyResult, PyObject, PyTuple, PyDict};

fn rust_function(py: Python, args: &PyTuple, kwargs: Option<&PyDict>) -> PyResult<PyObject> {
    let name: String = args.get_item(py, 0).extract(py)?;
    Ok(format!("Hello, {}!", name).to_py_object(py).into_object())
}

fn register_function(py: Python) -> PyResult<()> {
    let module = PyModule::new(py, "rust_module")?;
    module.add(py, "greet", py_fn!(rust_function))?;
    py.import("sys")?.get(py, "modules")?.set_item(py, "rust_module", module)?;
    Ok(())
}

性能敏感场景的优化

use cpython::{Python, PyResult, PyList};

fn sum_list(py: Python, list: &PyList) -> PyResult<f64> {
    let mut total = 0.0;
    
    // 直接迭代PyList避免不必要的转换
    for item in list.iter(py) {
        total += item.extract::<f64>(py)?;
    }
    
    Ok(total)
}

错误处理

use cpython::{Python, PyResult, PyErr};

fn safe_division(py: Python, a: f64, b: f64) -> PyResult<f64> {
    if b == 0.0 {
        Err(PyErr::new::<cpython::exc::ZeroDivisionError, _>(py, "division by zero"))
    } else {
        Ok(a / b)
    }
}

完整示例

下面是一个完整的Rust与Python互操作的示例,展示了如何从Rust调用Python函数并处理结果:

use cpython::{Python, PyResult, PyDict, PyModule, py_fn};

fn main() {
    // 1. 初始化Python解释器
    let gil = Python::acquire_gil();
    let py = gil.python();

    // 2. 调用Python内置函数
    if let Err(e) = call_python_builtins(py) {
        e.print(py);
    }

    // 3. 调用带关键字的Python函数
    if let Err(e) = call_with_keyword_args(py) {
        e.print(py);
    }

    // 4. 注册Rust函数到Python模块
    if let Err(e) = register_rust_function(py) {
        e.print(py);
    }

    // 5. 从Python调用刚才注册的Rust函数
    let result: String = py.eval("rust_module.greet('World')", None, None)
        .unwrap()
        .extract(py)
        .unwrap();
    println!("{}", result);
}

// 调用Python内置函数示例
fn call_python_builtins(py: Python) -> PyResult<()> {
    // 导入math模块
    let math = py.import("math")?;
    
    // 调用math.sqrt函数
    let result: f64 = math.call(py, "sqrt", (16.0,), None)?.extract(py)?;
    println!("Square root of 16 is {}", result);
    
    Ok(())
}

// 调用带关键字参数的Python函数示例
fn call_with_keyword_args(py: Python) -> PyResult<()> {
    let random = py.import("random")?;
    
    // 创建关键字参数字典
    let kwargs = PyDict::new(py);
    kwargs.set_item(py, "a", 1)?;
    kwargs.set_item(py, "b", 10)?;
    
    // 调用random.randint(a=1, b=10)
    let result: i32 = random.call(py, "randint", (1, 10), Some(&kwargs))?.extract(py)?;
    println!("Random number: {}", result);
    
    Ok(())
}

// Rust函数,将被注册到Python模块中
fn greet_function(py: Python, args: &PyTuple, _kwargs: Option<&PyDict>) -> PyResult<String> {
    let name: String = args.get_item(py, 0).extract(py)?;
    Ok(format!("Hello, {} from Rust!", name))
}

// 注册Rust函数到Python模块
fn register_rust_function(py: Python) -> PyResult<()> {
    let module = PyModule::new(py, "rust_module")?;
    module.add(py, "greet", py_fn!(greet_function))?;
    py.import("sys")?
        .get(py, "modules")?
        .set_item(py, "rust_module", module)?;
    Ok(())
}

注意事项

  1. 始终确保在持有GIL(全局解释器锁)的情况下访问Python对象
  2. 注意Python对象的生命周期管理,避免内存泄漏
  3. 考虑性能开销,尽量减少Rust和Python之间的数据转换
  4. 在多线程环境中要特别小心Python的GIL机制

cpython库为Rust和Python的互操作提供了强大而灵活的工具,特别适合需要将Python生态系统的丰富性与Rust的性能和安全性结合的场景。

回到顶部