Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pythonistaに捧ぐ、楽しさ駆動のRust活用術 〜テストヘルパーCLI開発編〜

Avatar for Hironori Yamamoto Hironori Yamamoto
September 18, 2025
32

Pythonistaに捧ぐ、楽しさ駆動のRust活用術 〜テストヘルパーCLI開発編〜

テーブルデータのテスト準備、その繰り返しの多さに「もっと楽にならないか?」と感じたことはありませんか?今回、そんなの日常的な悩みを解決するため、Rustで開発したパワフルなコード生成ツールを作成しました!RustでPythonのコードを解析するという、ちょっと不思議でギークなアプローチを通じて、型安全に退屈な作業を自動化するツールの開発方法についてお話します!

Avatar for Hironori Yamamoto

Hironori Yamamoto

September 18, 2025
Tweet

Transcript

  1. (※) 2023年10月時点 日本の医師のエムスリー会員率 エムスリーが事業展開している国の数 エムスリーが占める全世界で医師会員の割合 全世界で医師会員合計 17 カ国 (※) 50

    %以上 (※) 650 万人以上 (※) 90 %以上 エムスリー が展開する医療従事者向け情報 サイト「m3.com」は32万人を突破、日本 の医師の9割以上が会員。(※) 日本の医師の9割が登録するエムスリーのサービス グローバルでも医師の5割以上が会員、医療業界に根付く事業基盤 圧倒的な医療データへのアクセス容易性を活かしたAI開発 3
  2. そうだ、OSS を Rust で書こう! • ちょっとした業務の退屈な作業を改善 ◦ 業務に紐づくと継続的に使う • 開発環境の改善や本番コードへの影響が少ないところ

    • CLI 開発がオススメ! ◦ シングルバイナリで導入も簡単! • Rust 製ツールへのコントリ OSS は自分のやってみたい! を試すのにピッタリ
  3. • カラム一覧がわかる • カラム型がわかる • 型情報から関数の概要がわかる Pandera による型安全なデータ処理 Pandera DataFrame

    がある場合 class UserSchema(pa.DataFrameModel): activated: Series[bool] = pa.Field( description = “有効化フラグ” ) def select_activated( user: DataFrame[UserSchema] ) -> DataFrame[UserSchema]: … 型安全性によって管理を簡単に!
  4. # テストデータの準備 pd.DataFrame([ dict(id=1, age=20, prefecture=”福岡県”, activated=True, user_category=1, …) dict(id=2,

    age=30, prefecture=”東京都”, activated=False, user_category=2, …) ]).pipe(DataFrame[UserSchema]) 日常で見つけたデータ系のコードあるある 非正規化データのテスト・フィクスチャを作るのが大変 分析や ML バッチは非正規化データに持っていきがち
  5. # テストデータの準備 pd.DataFrame([ dict(id=1, age=20, prefecture=”福岡県”, activated=True, user_category=1, …) dict(id=2,

    age=30, prefecture=”東京都”, activated=False, user_category=2, …) ]).pipe(DataFrame[UserSchema]) 日常で見つけたデータ系のコードあるある 非正規化データのテスト・フィクスチャを作るのが大変 分析や ML バッチは非正規化データに持っていきがち 必要なカラムは activated ぐらいなのに😢
  6. # テストデータの準備 pd.DataFrame([ dict(id=1, age=20, prefecture=”福岡県”, activated=True, user_category=1, …) dict(id=2,

    age=30, prefecture=”東京都”, activated=False, user_category=2, …) ]).pipe(DataFrame[UserSchema]) 日常で見つけたデータ系のコードあるある 非正規化データのテスト・フィクスチャを作るのが大変 分析や ML バッチは非正規化データに持っていきがち JOIN とか複数データが絡むと🤯
  7. • 関数の引数は型安全 ◦ 󰢄: *args, **kwargs • 関数の戻り値にも型が欲しい • テストに関係ないカラムの設定は

    省略したい あるべきから考える、こうなってほしい 理想像から実装の計画を立てる やりたい書き方 user_schema に求める仕様 pd.DataFrame([ user_schema(activated=True), user_schema(activated=False), ]).pipe(DataFrame[UserSchema])
  8. • 関数の引数は型安全 ◦ 󰢄: *args, **kwargs • 関数の戻り値にも型が欲しい • テストに関係ないカラムの設定は

    省略したい あるべきから考える、こうなってほしい 理想像から実装の計画を立てる やりたい書き方 user_schema に求める仕様 pd.DataFrame([ user_schema(id=1, activated=True), user_schema(id=2, activated=False), ]).pipe(DataFrame[UserSchema]) テストロジックに影響がある値に専念できる
  9. class UserSchemaRecord(TypedDict): id: int activated: bool age: int … 動くサンプルコードを作ってみる

    量産は手間だが、TypedDict をデフォルト付きで返すだけ レコードの辞書に対応する TypedDict レコードを生成する関数 def user_schema( id: int = ランダムなデフォ activated: bool = ランダムなデフォ age: int = ランダムなデフォ … ) -> UserSchemaRecord return { “id”=id, “activated”=activated, … }
  10. 動くサンプルコードを作ってみる 量産は手間だが、TypedDict をデフォルト付きで返すだけ レコードの辞書に対応する TypedDict レコードを生成する関数 class UserSchemaRecord(TypedDict): id: int

    activated: bool age: int … def user_schema( id: int = ランダムなデフォ activated: bool = ランダムなデフォ age: int = ランダムなデフォ … ) -> UserSchemaRecord return { “id”=id, “activated”=activated, … } このコードが生成できれば目的達成!
  11. • 関数名: 型クラス名をスネークケース • 変数名: 型クラスの attribute • 変数の型: 型クラスの

    TypeAnnotation よくみるとコードに必要な情報はある? Pandera の型定義にすべて詰まっている! class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated: Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい Pandera の型定義 レコードを生成する関数で必要な要素
  12. • pa.DataFrameModel を継承した クラスの特定 • クラス名を取り出すことができる • attribute とその型一覧化できる 完全に理解した、方針を立てよう

    退屈なコードを自動生成できれば OK! Pandera の型定義 求められる機能 class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated: Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい
  13. 完全に理解した、方針を立てよう 退屈なコードを自動生成できれば OK! class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated:

    Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい Pandera の型定義 • pa.DataFrameModel を継承した クラスの特定 • クラス名を取り出すことができる • attribute とその型一覧化できる 求められる機能 構文解析の出番ですね
  14. • RustPython という Rust 製の Python Interpretor の一部 • Python

    で書かれたテキストを構文解析する • ruff の例からも分かるように高速! RustPython/Parser とは ruff にも使われていた Rust 製 Python Parser
  15. Python の構文解析と構文木 コードを木構造変換 →効率的にコードを処理 Module ├── ClassDef │ ├── name:

    "UserSchema" │ ├── bases: │ │ └── Attribute(pa.DataFrameModel) │ └── body: │ ├── AnnAssign │ │ ├── target: Name(id) │ │ ├── annotation: Subscript (Series[int]) │ │ └── value: Call(Fields) class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated: Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい
  16. 使い方は簡単 RustPython/Parser rustpython_parser::ast::Suite::parse(ファイル, パス) Module ├── ClassDef │ ├── name:

    "UserSchema" │ ├── bases: │ │ └── Attribute(pa.DataFrameModel) │ └── body: │ ├── AnnAssign │ │ ├── target: Name(id) │ │ ├── annotation: Subscript (Series[int]) │ │ └── value: Call(Fields) class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated: Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい
  17. 使い方は簡単 RustPython/Parser rustpython_parser::ast::Suite::parse(ファイル, パス) Module ├── ClassDef │ ├── name:

    "UserSchema" │ ├── bases: │ │ └── Attribute(pa.DataFrameModel) │ └── body: │ ├── AnnAssign │ │ ├── target: Name(id) │ │ ├── annotation: Subscript (Series[int]) │ │ └── value: Call(Fields) class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated: Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい 構文木はできた! どうやって処理すればいい?
  18. • pa.DataFrameModel を継承した クラスの特定 • クラス名を取り出すことができる • attribute とその型一覧化できる (再掲)完全に理解した、方針を立てよう

    退屈なコードを自動生成できれば OK! Pandera の型定義 求められる機能 class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated: Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい
  19. クラスの定義情報: ClassDef 継承元が pandera の DataFrame であるものを抽出 Module ├── ClassDef

    │ ├── name: "UserSchema" │ ├── bases: │ │ └── Attribute(pa.DataFrameModel)
  20. 型付きの代入文: AnnAssign Module ├── ClassDef │ └── body: │ ├──

    AnnAssign │ │ ├── target: Name(id) │ │ ├── annotation: Subscript (Series[int]) │ │ └── value: Call(Fields) Pandera で定義されている attribute の抽出
  21. • pa.DataFrameModel を継承した クラスの特定 → ClassDef • クラス名を取り出すことができる → ClassDef

    • attribute とその型一覧化できる → AnnAssign (再掲)完全に理解した、方針を立てよう 退屈なコードを自動生成できれば OK! Pandera の型定義 求められる機能 class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated: Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい
  22. • visitor パターン ◦ 構文解析の探索のテンプレートを提供する ◦ 訪問先のノードの処理をカスタマイズ可能 • 今回で変更した部分 ◦

    import 文で別ファイルにあるクラスを参照 visitor パターンを提供する Trait も 構文解析は深さ優先探索; これをサポートするパターン
  23. • visitor パターン ◦ 構文解析の探索のテンプレートを提供する ◦ 訪問先のノードの処理をカスタマイズ可能 • 今回で変更した部分 ◦

    import 文で別ファイルにあるクラスを参照 • visitor パターンを提供する Trait も 構文解析は深さ優先探索; これをサポートするパターン admin.py from user import UserSchema class AdminSchema(UserSchema): # AdminSchema も Pandera DataFrame user.py class UserSchema(pa.DataFrameModel): id: Series[int] = Fields(...) activated: Series[bool] = Fields(...) age: Series[int] = Fields(...) … カラムがいっぱい
  24. • maturin: CLI バイナリを wheel 同梱 • リリース: maturin-action ◦

    cross-platform build リリースまでの道のり Rust ライブラリ ビルド周り • clap: CLI ライブラリ • askama: テンプレート • anyhow: いつもの Python ツールとして公開したい!
  25. リリースまでの道のり Python ツールとして公開したい! • maturin: CLI バイナリを wheel 同梱 •

    リリース: maturin-action ◦ cross-platform build Rust ライブラリ ビルド周り • clap: CLI ライブラリ • askama: テンプレート • anyhow: いつもの
  26. リリースまでの道のり Python ツールとして公開したい! • maturin: CLI バイナリを wheel 同梱 •

    リリース: maturin-action ◦ cross-platform build Rust ライブラリ ビルド周り • clap: CLI ライブラリ • askama: テンプレート • anyhow: いつもの 読み方はふぉーじぇん
  27. • ちょっとした裏技開発 • コード生成による ボイラープレートの 簡略化 • 業務と絡めてモチベup! • 普段使わない技術を!

    • CI/CD の整備など、 基盤っぽいところも! まとめ pandera 是非導入を! OSS はちょうどいい砂場 構文解析はおもしろい! • Any な DataFrame に別れを告げよう • 型安全に安定した開発を • テストコードも書こう