Python 套件推薦 - rustimport 給你的 Python 一對翅膀

Posted on  Sep 18, 2023  in  Python 模組/套件推薦 , Python 程式設計 - 高階  by  Amo Chen  ‐ 4 min read

不可否認 Python 以其優異的生態系與社群資源為開發帶來速度優勢。

不過每個應用都有效率極限, Python 當然也不例外,而且 Python 天生比起 JavaScript, Java 等語言更容易遇到效能瓶頸,這時候通常有 3 種選擇:

  1. 改用效率較好的寫法或套件
  2. 部分功能/元件改用別的語言實作
  3. 全部用別的語言實作

選項 1 是最簡單的,只要能夠找到更快的寫法或者更好的套件,就能夠緩解問題,再撐一陣子;選項 3 則是最困難的,要有足夠的時間以及負責人的支持之下,才有可能進行,畢竟所有的功能都必須改寫之外,測試也是需要全部重新來過,更何況通常公司專案都有時程壓力,選項 3 通常都是難以說服高層的選項;至於選項 2 則是折衷選項,建議選項 1 已經無法解決問題時採用,目前很多 Python 套件為了效率也都會採用選項 2 的做法,例如 orjson 就是以 Rust 語言實作的 JSON parser 。

目前 Python 與 Rust 的介接主要靠 PyO3 , 不過步驟稍微複雜一些,本文將介紹如何透過 rustimport 套件,極度簡化介接 Python 與 Rust 的方法,讓開發 Python 擴充套件可以更快樂、簡便!

本文環境

$ curl https://sh.rustup.rs | sh
$ pip install rustimport

rustimport 簡介

rustimport 套件是 1 套受到 cppimport 所啟發的 Python 套件,是專門用來介接 Python 與 Rust 的 Python 套件,可以讓開發者在 Python 中呼叫以 Rust 寫成的擴充方法、類別,藉此提升 Python 部分效能,更精確的說,介接的功能是由 PyO3rust-cpython 所提供, rustimport 是簡化整個流程,讓開發過程更加輕鬆簡便。

p.s. ccpimport 則專門用來介接 C++ 與 Python

rustimport 推薦使用 PyO3 作為介接 Python 的第三方套件,所以 rustimport 的範例也都是以使用 PyO3 為主,不過 rustimport 也提供使用 rust-cpython範例可以參考。

rustimport 範例

以下是 1 個極簡的 Rust 程式,這個程式中定義 1 個 function square 用以算傳入值的平方數,其傳入與回傳值皆為 32-bit 的整數(integer), 為了讓 Python 也能呼叫此函數,因此加上 #[pyfunction] 屬性(attribute),如此一來 PyO3 就能夠知道 square 要編譯成 Python 也能呼叫的格式。

// rustimport:pyo3 則是 rustimport 要求必須加的註解,代表 rustimport 處理這個檔案時需使用 PyO3 作為介接 Python 與 Rust 的套件(是的, cppimport 也是使用類似的註解作法)。

rmath.rs 的內容:

// rustimport:pyo3

use pyo3::prelude::*;

#[pyfunction]
fn square(x: i32) -> i32 {
    x * x
}

弄好上述檔案之後,可以打開 Python 直譯器(interpreter),輸入 import rustimport.import_hook 使用 rustimport 提供的擴充,該擴充會負責在 Python import Rust 寫成的模組時,負責編譯 Rust 語言寫成的檔案,例如以下 import rmath 之後,就會看到 rustimport 開始幫忙編譯 rmath.rs :

>>> import rustimport.import_hook
>>> import rmath
>>>     Updating crates.io index
>>>   Downloaded pyo3-build-config v0.18.3
>>>   ......(skipped)......
>>>    Compiling rmath v0.1.0 (/private/var/folders/b_/3v4gb5ts6hqgym60_qh3n8k40000gn/T/rustimport/rmath-c47236b26467c41cc524658bcbec161d/rmath)
>>>     Finished dev [unoptimized + debuginfo] target(s) in 9.37s

編譯完成之後,可以看到 rmath.rs 已經被編譯成 rmath.cpython-38-darwin.so shared library, 如此一來就 rmath 就變成 importable 的模組:

.
├── rmath.cpython-38-darwin.so
└── rmath.rs

試著呼叫看看 rmath.square(2) 也可以發現正常運作:

>>> rmath.square(2)
>>> 4

就這麼簡單,你已經完成 1 個用 Rust 寫成的 Python 模組!

建立 Python 擴充的指令

rustimport 提供 2 種建立 Python 擴充的指令, 1 種為單一檔案的形式,如前述的 rmath.ts ,適合單純的函數、類別:

$ python3 -m rustimport new <檔名>.rs

另一種則是建立 1 個更正式的 Rust 專案,這種方法適合有使用較多套件或複雜一些的 Rust 擴充:

$ python3 -m rustimport new <擴充名>

第 2 種指令執行後,會產生類似以下的資料夾結構,其中 Cargo.toml 就存著 Rust 專案的相關設定,例如 package name, dependencies 等等:

my_rust_project
├── Cargo.toml
└── src
    └── lib.rs

Go to Production

如果要實際使用在 Production 環境,最好預先編譯好生產環境需要 shared library, 避免使用 rustimport.import_hook 在 import 時才進行編譯,因此可以簡單使用指令進行編譯:

$ python -m rustimport build --release <檔名>.rs

$ python -m rustimport build --release <資料夾>

參數 --release 會讓 cargo 使用最佳化過的設定進行編譯,避免 使用 debug mode 編譯 shared library 造成 Rust 效能不好的情況,所以請記得要加 --release 參數。

如果是在 Python 直譯器中,可以用以下方式打開 release mode:

import rustimport
rustimport.settings.compile_release_binaries = True

或者設定環境變數 RUSTIMPORT_RELEASE_BINARIEStrue :

$ export RUSTIMPORT_RELEASE_BINARIES=true

總結

rustimport 套件極度簡化介接 Python 與 Rust 的方法,讓用 Rust 開發 Python 擴充的過程更迷人,不過 rustimport 在打包(packaging)的嚴謹程度仍比不上 setuptools-rustMaturin 這 2 個套件,所以使用上個人建議還是應用在相對單純的 Rust 程式碼較好,如果過於複雜,建議還是使用 setuptools-rustMaturin 開發會好一些。

此外,本文以介紹 rustimport 套件為主,關於更多如何撰寫 Rust 程式碼以及編譯成 Python importable module 的方式,請參閱 Rust 官網以及 PyO3 官網。

以上!

Happy Coding!

References

mityax/rustimport: Import Rust source files directly from Python!

The easiest way to speed up Python with Rust

pyo3 - Rust

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!