Python 套件推薦 - rustimport 給你的 Python 一對翅膀
Posted on Sep 18, 2023 in Python 模組/套件推薦 , Python 程式設計 - 高階 by Amo Chen ‐ 4 min read
不可否認 Python 以其優異的生態系與社群資源為開發帶來速度優勢。
不過每個應用都有效率極限, Python 當然也不例外,而且 Python 天生比起 JavaScript, Java 等語言更容易遇到效能瓶頸,這時候通常有 3 種選擇:
- 改用效率較好的寫法或套件
- 部分功能/元件改用別的語言實作
- 全部用別的語言實作
選項 1 是最簡單的,只要能夠找到更快的寫法或者更好的套件,就能夠緩解問題,再撐一陣子;選項 3 則是最困難的,要有足夠的時間以及負責人的支持之下,才有可能進行,畢竟所有的功能都必須改寫之外,測試也是需要全部重新來過,更何況通常公司專案都有時程壓力,選項 3 通常都是難以說服高層的選項;至於選項 2 則是折衷選項,建議選項 1 已經無法解決問題時採用,目前很多 Python 套件為了效率也都會採用選項 2 的做法,例如 orjson 就是以 Rust 語言實作的 JSON parser 。
目前 Python 與 Rust 的介接主要靠 PyO3 , 不過步驟稍微複雜一些,本文將介紹如何透過 rustimport 套件,極度簡化介接 Python 與 Rust 的方法,讓開發 Python 擴充套件可以更快樂、簡便!
本文環境
- Python 3
- Rust
- rustimport
$ curl https://sh.rustup.rs | sh
$ pip install rustimport
rustimport 簡介
rustimport 套件是 1 套受到 cppimport 所啟發的 Python 套件,是專門用來介接 Python 與 Rust 的 Python 套件,可以讓開發者在 Python 中呼叫以 Rust 寫成的擴充方法、類別,藉此提升 Python 部分效能,更精確的說,介接的功能是由 PyO3 或 rust-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_BINARIES
為 true
:
$ export RUSTIMPORT_RELEASE_BINARIES=true
總結
rustimport 套件極度簡化介接 Python 與 Rust 的方法,讓用 Rust 開發 Python 擴充的過程更迷人,不過 rustimport 在打包(packaging)的嚴謹程度仍比不上 setuptools-rust 與 Maturin 這 2 個套件,所以使用上個人建議還是應用在相對單純的 Rust 程式碼較好,如果過於複雜,建議還是使用 setuptools-rust 或 Maturin 開發會好一些。
此外,本文以介紹 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