4.1 python中調用rust程序


概述

使用rust-cpython將rust程序做為python模塊調用;

通常為了提高python的性能;

 

參考

https://github.com/dgrunwald/rust-cpython

 

創建rust lib庫

cargo new rust2py --lib

或者使用IDE創建一個rust lib庫項目

 

Cargo.toml

[package]
name = "rust2py"
version = "0.1.0"
edition = "2018"


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

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

 

lib.rs

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

#[macro_use]
extern crate cpython;

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


pub fn print_str(a: String) -> String {
    print!("{:#?}",a);
    a
}

pub fn print_str_py(_: Python, a: String) -> PyResult<String>{
    let mm = print_str(a);
    Ok(mm)
}


// logic implemented as a normal rust function
fn sum_as_str(a:i64, b:i64) -> String {
    format!("{}", a + b).to_string()
}

// rust-cpython aware function. All of our python interface could be
// declared in a separate module.
// Note that the py_fn!() macro automatically converts the arguments from
// Python objects to Rust values; and the Rust return value back into a Python object.
fn sum_as_str_py(_: Python, a:i64, b:i64) -> PyResult<String> {
    let out = sum_as_str(a, b);
    Ok(out)
}

py_module_initializer!(rust2py, init_rust2py, PyInit_rust2py, |py, m| {
    m.add(py, "__doc__", "This module is implemented in Rust.")?;
    m.add(py, "print_str", py_fn!(py, print_str_py(a: String)))?;
    m.add(py, "sum_as_str", py_fn!(py, sum_as_str_py(a: i64, b:i64)))?;
    Ok(())
});

 注意:py_module_initializer方法的參數的中rust2py一定要與模塊的名稱一致,這個不是隨便寫的字符串名稱,比如PyInit_rust2py就表示將來在python中調用的模塊名稱是rust2py

 

編譯並復制到python的模塊

cargo build 
cp target/debug/librust2py.so /opt/app/anaconda3/lib/python3.8/site-packages/rust2py.so

注意:復制到python模塊的so沒有lib前綴

可以換一個正規的python模塊名稱, 效果是一樣的, 但這樣的名字看起來更"專業"一點 ^_^

ai@aisty:/opt/app/anaconda3/envs/py37/lib/python3.7/site-packages$ mv rust2py.so rust2py.cpython-37m-x86_64-linux-gnu.so
ai@aisty:/opt/app/anaconda3/envs/py37/lib/python3.7/site-packages$ ll rust2py.cpython-37m-x86_64-linux-gnu.so 
-rwxrwxr-x 1 ai ai 5101552 12月  2 10:52 rust2py.cpython-37m-x86_64-linux-gnu.so*

 

封裝一個自動安裝的腳本install.sh

#!/bin/bash

cd /opt/wks/rust/rfil/rust2py/
/home/ai/.cargo/bin/cargo build
cp target/debug/librust2py.so /opt/app/anaconda3/envs/py37/lib/python3.7/site-packages/rust2py.cpython-37m-x86_64-linux-gnu.so

每次修改,執行一下腳本就會覆蓋上一次的結果

(py37) ai@aisty:/opt/wks/rust/rfil/rust2py$ chmod +x install.sh 
(py37) ai@aisty:/opt/wks/rust/rfil/rust2py$ ./install.sh 

 

 

其他安裝參考

https://github.com/PyO3/setuptools-rust

 

 

python調用模塊

ai@aisty:/opt/app/anaconda3/lib/python3.8/site-packages$ python3.8
Python 3.8.5 (default, Sep  4 2020, 07:30:14) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import rust2py
>>> rust2py.sum_as_str(2,5)
'7'
>>> rust2py.print_str("from rust")
'from rust'
>>> 

 

接下來添加一個稍微復雜的方法:統計列表中元素的個數,輸入Python列表,返回Python字典

fn elem_count(py: Python, pl: PyList) -> PyResult<PyDict> {
    let dt = PyDict::new(py);
    for e in pl.iter(py) {
        let el = &e;
        let ct = dt.contains(py,el).unwrap();

        if ct {
            let a = dt.get_item(py,el).unwrap().extract::<i32>(py).unwrap() + 1 ;
            dt.set_item(py,el,a)?;
        }else {
            dt.set_item(py,el,1)?;
        }

    }
    Ok(dt)
}

// https://dgrunwald.github.io/rust-cpython/doc/src/cpython/objects/dict.rs.html#129-143


py_module_initializer!(rust2py, init_rust2py, PyInit_rust2py, |py, m| {
    m.add(py, "__doc__", "This module is implemented in Rust.")?;
    m.add(py, "print_str", py_fn!(py, print_str_py(a: String)))?;
    m.add(py, "sum_as_str", py_fn!(py, sum_as_str_py(a: i64, b:i64)))?;
    m.add(py, "hello", py_fn!(py, hello_py()))?;
     m.add(py, "elem_count", py_fn!(py, elem_count(pl: PyList)))?;
    Ok(())
});

rust中的python方法通常返回一個PyResult,這是一個Python對象或Python異常的枚舉,使用.unwrap()將之解析為一個Python對象,然后就可以調用Python對象的方法了,這些方法可以從后面介紹的文檔上查看

>>> import rust2py
>>> rust2py.elem_count([1,2,3])
{1: 1, 2: 1, 3: 1}
>>> rust2py.elem_count([1,2,3,3])
{1: 1, 2: 1, 3: 2}

 

更多數據類型方法請參考

http://dgrunwald.github.io/rust-cpython/doc/cpython/

 

如果想知道更多的關於如何使用一個Py對象的細節,請看上面文件源碼

每個py對象,后面都有一個[src]的標記,這是個超鏈接,點開之后會轉向源碼,比如PyDict,源碼中有測試代碼,對用法學習很有幫助

https://dgrunwald.github.io/rust-cpython/doc/src/cpython/objects/dict.rs.html#129-143

    #[test]
    fn test_items_list() {
        let gil = Python::acquire_gil();
        let py = gil.python();
        let mut v = HashMap::new();
        v.insert(7, 32);
        v.insert(8, 42);
        v.insert(9, 123);
        let dict = v.to_py_object(py);
        // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
        let mut key_sum = 0;
        let mut value_sum = 0;
        for el in dict.items_list(py).iter(py) {
            let tuple = el.cast_into::<PyTuple>(py).unwrap();
            key_sum += tuple.get_item(py, 0).extract::<i32>(py).unwrap();
            value_sum += tuple.get_item(py, 1).extract::<i32>(py).unwrap();
        }
        assert_eq!(7 + 8 + 9, key_sum);
        assert_eq!(32 + 42 + 123, value_sum);
    }

看源碼,是最直接,直達本質的快捷學習通道!

 

全代碼

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

#[macro_use]
extern crate cpython;

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


pub fn print_str(a: String) -> String {
    print!("{:#?}",a);
    a
}

pub fn print_str_py(_: Python, a: String) -> PyResult<String>{
    let mm = print_str(a);
    Ok(mm)
}


// logic implemented as a normal rust function
fn sum_as_str(a:i64, b:i64) -> String {
    format!("{}", a + b).to_string()
}

// rust-cpython aware function. All of our python interface could be
// declared in a separate module.
// Note that the py_fn!() macro automatically converts the arguments from
// Python objects to Rust values; and the Rust return value back into a Python object.
fn sum_as_str_py(_: Python, a:i64, b:i64) -> PyResult<String> {
    let out = sum_as_str(a, b);
    Ok(out)
}

fn hello_py(py: Python) -> PyResult<String> {
    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)?;

    let res = format!("Hello {}, I'm Python {}", user, version).to_string();
    let res = res.replace("\n","");
    Ok(res)
}

fn elem_count(py: Python, pl: PyList) -> PyResult<PyDict> {
    let dt = PyDict::new(py);
    for e in pl.iter(py) {
        let el = &e;
        let ct = dt.contains(py,el).unwrap();
        // let ct2 = match ct {
        //     Ok(b) => b,
        //     Err(e) => return Err(e),
        // };

        if ct {
            let a = dt.get_item(py,el).unwrap().extract::<i32>(py).unwrap() + 1 ;
            dt.set_item(py,el,a)?;
        }else {
            dt.set_item(py,el,1)?;
        }

    }
    Ok(dt)
}

// https://dgrunwald.github.io/rust-cpython/doc/src/cpython/objects/dict.rs.html#129-143


py_module_initializer!(rust2py, init_rust2py, PyInit_rust2py, |py, m| {
    m.add(py, "__doc__", "This module is implemented in Rust.")?;
    m.add(py, "print_str", py_fn!(py, print_str_py(a: String)))?;
    m.add(py, "sum_as_str", py_fn!(py, sum_as_str_py(a: i64, b:i64)))?;
    m.add(py, "hello", py_fn!(py, hello_py()))?;
     m.add(py, "elem_count", py_fn!(py, elem_count(pl: PyList)))?;
    Ok(())
});

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM