iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
0
自我挑戰組

WebAssembly + Rust 的前端應用系列 第 20

[Day 20] Rust FFI with Python

大家好今天要來試試看用 FFI 來接 Python 回傳的資料,不過筆者其實也不確定能不能成功,因為官網給的說明也很少不過沒關係我們還是可以嘗試看看,那麼先來介紹一下FFI。

FFI(Foreign Function Interface)

其顧名思義就是和不同的程式語言溝通的介面,例如 C/C++ 等等因此我們可以這樣寫,

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

呼叫 C 的函式並且得到運算後的結果,但是要注意的因為 Rust 不能檢查其他程式是否安全所以都必須要放在 unsafe 的 block 裡面才可以運作。

而 Rust 也可以創建一個接口讓其他的程式呼叫他不過這不是今天的主題,有興趣的人可以到下面的參考連結。

PyO3

筆者搜尋之後找到 PyO3 這個 library 而他提供可以讓 python 使用 Rust 函式做運算反之亦然或許這就是我們要的,來試試吧!

因為這個依賴只支援 Rust nightly 的版本所以我們必須先安裝並且把它設定為 default

$ rustup toolchain install nightly
$ rustup default nightly

接著再來新增依賴,

[dependencies]
pyo3 = "0.8.0"

然後我們直接照著他的範例試試看可不可以跑得起來,

use pyo3::prelude::*;
use pyo3::types::IntoPyDict;

fn main() -> PyResult<()> {
    let gil = Python::acquire_gil();
    let py = gil.python();
    let sys = py.import("sys")?;
    let version: String = sys.get("version")?.extract()?;

    let locals = [("os", py.import("os")?)].into_py_dict(py);
    let code = "os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'";
    let user: String = py.eval(code, None, Some(&locals))?.extract()?;

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

這段程式碼很簡單基本上就是從 Rust 裡面調用 Python 的程式然後取得一些資訊。

https://ithelp.ithome.com.tw/upload/images/20191006/20119807qkH4oIn8i7.png

執行結果看起來是 python 少了一些 library 還有環境變數的問題,後來改成用pyenv解決了。(真是好東西!)/images/emoticon/emoticon08.gif

成功的執行結果,

https://ithelp.ithome.com.tw/upload/images/20191006/20119807DN2LFryibI.png

接下來我們可以試試看直接把翻譯的程式 import 進來,首先把 python 程式全部複製到 src 裡面,

https://ithelp.ithome.com.tw/upload/images/20191006/20119807f348T7StAt.png]

接著我們需要稍微修改一下 python 的程式

檔案名稱:run_nn.py

def translate(original_sentence):
    # preprocessing the original_sentence
    init_jieba_dict()
    sentence = seg2words(original_sentence)
    sentence = preprocess_ch_sentence(sentence)
    
    ...

把原本用 argv 傳的參數改成函式的參數,並且把最下面 main 的判斷式都拿掉

if __name__ == '__main__':

    # if len(sys.argv) != 2 or sys.argv[1] not in ['train','translate']:
    #     raise ValueError("""usage: python run_nn.py [train/translate]""")

    if sys.argv[1] == 'train':
        train()
    
    if sys.argv[1] == 'translate':
        translate()

這段全部刪除。

接著來改寫剛剛的 rust 程式,

fn main() -> PyResult<()> {
    let gil = Python::acquire_gil();
    let py = gil.python();
    let run_nn = py.import("./python-lib/run_nn")?;
    let result = run_nn.translate("你好");
    // let sys = py.import("sys")?;
    // let version: String = sys.get("version")?.extract()?;
    // let locals = [("os", py.import("os")?)].into_py_dict(py);
    // let code = "os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'";
    // let user: String = py.eval(code, None, Some(&locals))?.extract()?;
    println!("{}", result);
    Ok(())
}

躍躍欲試了嗎?執行結果,

https://ithelp.ithome.com.tw/upload/images/20191006/20119807YXwpAKkQlc.png

可能要讓各位失望了本次實驗結果宣告失敗~這個 library 目前無法支援比較複雜的程式詳情請參考這個 issue這篇說明。

總結

雖然今天的目標沒有達成不過未來確實是有機會的,雖然不能用 FFI 的方式呼叫 Python 至少我們還是可以透過 Cli 沒有問題。接下來會回到我們的主題 Rust 跟 Webassembly 不過筆者還需要多一天的時間準備讀書會,所以明天會繼續開發跟研究 Actix 的潛在功能~

那麼我們明天見~

最後一樣有問題歡迎發問

/images/emoticon/emoticon07.gif

參考網站

Foreign,Function,Interface

python_from_rust


上一篇
[Day 19] Rust Actix Python 程式呼叫 (2)
下一篇
[Day 21] Rust Actix PART2
系列文
WebAssembly + Rust 的前端應用30

尚未有邦友留言

立即登入留言