iT邦幫忙

0

Rust與AI/Python完美的結合 (1)

  • 分享至 

  • xImage
  •  

前言

上一篇【以Rust開發一個網站,不是網頁喔!】介紹以Rust開發一個網站,這次我們再聊聊【Rust如何與Python整合】。

Rust與Python整合的方式有兩種:

  1. Rust與Python程式互相呼叫。
  2. 流程的整合:在應用系統開發的過程中,擷取Rust與Python的優點,同時使用兩種語言,各自完成擅長的任務。

以下我們就分別以簡單的範例說明。

Rust與Python程式互相呼叫

越來越多的Python套件改用Rust語言開發,例如:

  1. Polars:克服Pandas套件效能的問題。
  2. Pydantic:為資料驗證(data validation)的套件,v2.0改以Rust語言開發。
  3. tiktoken:為ChatGPT分詞器(BPE tokenizer)。
  4. uv:目前被熱烈討論的套件管理工具。

以Polars為例,讀入檔案的速度約比Pandas快7倍,又因為Rust提供【跨語言呼叫介面】(Foreign Function Interface, FFI),因此,很多Rust開發的套件都同時支援Rust/Python API介面,以下我們來實作一下。

Python呼叫Rust

Pyo3套件專門提供Rust/Python相互呼叫的函數庫,包覆FFI相關的支援,包括資料型別的轉換及函數呼叫的規定,因此,我們就直接以Pyo3套件實作。

因為,Rust效能遠比Python好,因此,常常會把需要注重效能的商業邏輯以Rust開發,例如大量資料需以迴圈逐筆計算,使用Rust不僅效能較好,記憶體使用也較節省。

以下實作階層(factorial)計算,程序如下:

  1. 安裝maturin:maturin可協助建立Wheel安裝檔,方便Rust程式包裝成套件,方便安裝及提供Python呼叫。
pip install maturin
  1. 建立新專案目錄,例如PythonCallRust,並在該目錄開啟終端機或cmd,執行下列指令建立專案。
maturin init

選擇Pyo3為骨架,並設專案名稱為test1。

  1. 在test1目錄執行下列指令,安裝Pyo3套件。
cargo add pyo3
  1. 修改src/main.rs內容:
use pyo3::prelude::*;

// 階層(factorial)計算
#[pyfunction]
fn factorial(a: i32) -> PyResult<i32> {
    let mut sum1:i32 = 1;
    for i in 1..(a+1) {
        sum1 = sum1 * i;
    }
    Ok(sum1)
}

// 註冊factorial函數
#[pymodule]
fn test1(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(factorial, m)?)?;
    Ok(())
}
  1. 執行下列指令建置專案,並安裝套件以提供Python呼叫。
maturin develop
  1. 以Python呼叫上述Rust程式,開啟另一終端機或cmd,執行python指令。
import test1
test1. factorial(5)

會得到12345 = 120。

就是那麼簡單,含兩個步驟:建立Rust函數,並註冊函數即可。

Rust呼叫Python

相反的,也可以使用Rust呼叫Python,但什麼時候會用到呢? Rust畢竟還是相對年輕的語言,套件功能還不那麼完整,例如想要繪製統計圖,使用Python只要幾行就搞定了,以下就來實驗這個情境。

  1. 建立新專案:執行下列指令建立專案call_python。
cargo new call_python
  1. 先測試inline的方式,在Rust程式中以字串嵌入Python程式碼。
    Python::with_gil(|py| {
        // Deprecated since 0.23.0: renamed to PyModule::from_code
        let fun: Py<PyAny> = PyModule::from_code_bound(
            py,
            "def example(*args, **kwargs):
                if args != ():
                    print('called with args', args)
                if kwargs != {}:
                    print('called with kwargs', kwargs)
                if args == () and kwargs == {}:
                    print('called with no arguments')",
            "",
            "",
        )?
        .getattr("example")?
        .into();

使用PyModule::from_code_bound包覆Python程式碼,上面範例很簡單,就是接收各種參數類別,包括一般參數(*args)及命名參數(**kwargs),並列印出來。

  1. 接著就可以加入測試碼:fun.call0表呼叫Python不帶參數,fun.call1帶1個參數。
        // call object without any arguments
        fun.call0(py)?;

        // pass object with Rust tuple of positional arguments
        let arg1 = "arg1";
        let arg2 = "arg2";
        let arg3 = "arg3";
        let args = (arg1, arg2, arg3);
        fun.call1(py, args)?;

        // call object with Python tuple of positional arguments
        let args = PyTuple::new_bound(py, &[arg1, arg2, arg3]);
        fun.call1(py, args)?;
  1. 測試:需先設定Python直譯器的檔案路徑。
set PYTHONHOME=C:\Users\<使用者帳號>\anaconda3
set PYTHONPATH=C:\Users\<使用者帳號>\anaconda3
cargo run
  1. 輸出結果:
called with no arguments
called with args ('arg1', 'arg2', 'arg3')
called with args ('arg1', 'arg2', 'arg3')
  1. 將Python程式碼獨立儲存成一個檔案,假設為example.py,內容如下,主要是讀取計程車小費(tips)資料集,並以日期(day)欄位為X軸,小費(tip)為Y軸,繪製長條圖(Bar)。
import matplotlib.pyplot as plt
import seaborn as sns

# Load an example dataset with long-form data
df = sns.load_dataset("tips")

# Plot the responses for different events and regions
fig, ax = plt.subplots(figsize=(8, 6))
sns.barplot(x="day", y="tip", hue="day", data=df)
ax.get_legend().remove()            
plt.show()    
  1. 執行example.py。
     let code = std::fs::read_to_string("example.py").unwrap();
     let _ = Python::with_gil(|py| -> PyResult<()> {
        PyModule::from_code_bound(py, &code, "example.py", "example")?;
        Ok(())
    });
  1. 執行cargo run,輸出結果:
    https://ithelp.ithome.com.tw/upload/images/20240827/20001976Mo4RVw53K9.png

  2. 將example.py置換如下,並先安裝nicegui套件,它是網頁開發套件。

from pathlib import Path

from nicegui import app, ui
from nicegui.events import KeyEventArguments

folder = Path(__file__).parent / 'slides'  # image source: https://pixabay.com/
files = sorted(f.name for f in folder.glob('*.jpg'))
index = 0


def handle_key(event: KeyEventArguments) -> None:
    global index
    if event.action.keydown:
        if event.key.arrow_right:
            index += 1
        if event.key.arrow_left:
            index -= 1
        index = index % len(files)
        slide.set_source(f'slides/{files[index]}')


app.add_static_files('/slides', folder)  # serve all files in this folder
slide = ui.image(f'slides/{files[index]}')  # show the first image
ui.keyboard(on_key=handle_key)  # handle keyboard events

ui.run(port=8000)
  1. 新增slides目錄,並在目錄放入一些圖片檔案。
  2. 執行cargo run,並在瀏覽器輸入http://127.0.0.1:8000/ ,如下圖,可使用往左、往右鍵顯示上一張或下一張圖片。
    https://ithelp.ithome.com.tw/upload/images/20240827/20001976tM67btOmlc.jpg

結語

以上範例說明如何進行跨語言呼叫,結合Rust及Python優點,既可提升系統效能,又可提升編程生產力,下一次將繼續說明Rust與AI的整合。

工商廣告:)

Rust雖然具備諸多優點,但學習曲線陡峭,因此筆者最近剛完成【Rust 最佳入門與實戰】一書的撰寫,希望能與讀者分享Rust開發心得,內容除了Rust語言的入門、設計典範(Design patterns)外,也著重應用的探討,包括網頁、WebAssembly、桌面程式、資料庫、機器學習/深度學習、區塊鏈…等。
https://ithelp.ithome.com.tw/upload/images/20240817/20001976QxDOTVaiEa.jpg

本文相關範例放在這裡,【Rust 最佳入門與實戰】還有各式各樣的範例供大家下載。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言