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

Streamlitの細かい話

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 Streamlitの細かい話

Avatar for NISHIKAWA, Daisuke

NISHIKAWA, Daisuke

March 16, 2025
Tweet

More Decks by NISHIKAWA, Daisuke

Other Decks in Technology

Transcript

  1. AI 3 ▪ 西川 大亮 ▪ 2019/4 DeNA入社、2020/4 GO転籍(当時Mobility Technologies)

    ▪ 前職: 中堅SIer ▪ 14年研究所、3年コンサル ▪ 顧客向け技術紹介と火消しが主な仕事 (Projが燃える匂いには敏感) ▪ 趣味 ▪ ゴルフ(年数回) ▪ 競馬予想(サボり気味) ▪ 設計欲を満たせるゲームにハマりやすい ▪ やっていること ▪ タクシーアプリ『GO』(主に乗務員端末)の挙動解析 ▪ 課題解決:原因調査→類型化→自動検知 ▪ 性能改善:現状把握→KPI定義→レポート+指針策定 ▪ システムよりも人間系の解析が中心 自己紹介
  2. AI 4 背景(Streamlitとは) • https://streamlit.io/ ◦ Streamlit turns data scripts

    into shareable web apps in minutes. ◦ All in pure Python. No front‑end experience required. • 国内だと“PythonのWebアプリフレームワーク” と表現されることが多い • GO Inc. では主にBiz向けの情報共有ツールとして 活躍している ◦ “グラフや統計値では意味が取れないが、スプ シだと量が多くて辛い” ぐらいの粒度で使う • 簡単さを重視する分、お約束が多くて癖がある • 今回はその細かい癖のお話 調査時のStreamlitのバージョン: 1.41.1 地図に位置を表示するだ けならこのぐらいで書け る データ: bigquery-public-data.chicago_ta xi_trips.taxi_trips 注 Speaker Deckだとアニ メーションGIFが動きません ごめんなさい…
  3. AI • streamlit run コマンドの引数が開始地点 ◦ streamlit run app.py ならapp.pyから実行される

    • なので、vscodeなどでのデバッグ実行も同じ(右) ◦ invokeコマンドをデバッグするのと同じ形 6 どこから始まるのか?(起動の細かい話①) • ユーザーコードの開始地点の話 • 自分で環境を作っていると自明だが、既に環境がある とdockerやkubanetesのファイルに紛れてわからなく なることも // streamlit 挙動確認用 { "name": "streamlit", "type": "debugpy", "request": "launch", "justMyCode": false, "console": "integratedTerminal", "cwd": "${workspaceFolder}", "env": { "PYTHONPATH": "${workspaceFolder}", }, "module": "streamlit", "args": [ "run", "${workspaceFolder}/apps.py", ] } • (おまけ)実行は別スレッド ◦ スレッドなので複数のブラウザから同時にリクエス トしても処理するのは1つずつになる ◦ 並列処理はWSGI(uwsgi, gunicorn, etc.)を使うこと になりそう
  4. AI 7 いつ始まるのか?(起動の細かい話②) • ユーザーコードの起動タイミングの話 • コマンドラインで指定しているのだからそのタイミン グ…ではない。Webアプリなので • ブラウザ等からリクエストがあったタイミングで呼び

    出される ◦ JavaScriptが実行できないとダメ ▪ 単にcurlだと届かない ▪ デフォルトページはStreamlit側で出している ◦ もしE2Eテストしたいなら、Seleniumなどが必要 ◦ 内部はスレッドなので、処理が複数あると前が終わ るまで待たされる
  5. AI 8 UI操作時はどこから始まるのか?(起動の細かい話③) • ブラウザでURLを叩くと、コマンドで渡したファイルが起動する なら、画面のボタンを押した時は? • 通常はコマンドで渡したファイルが先頭から呼ばれる ◦ ボタンのコードからではない

    ◦ 右上のように書くと、1行目から開始し、if文を通って最初は6 行目が、ボタンを押すと4行目が実行される。 • 例外その1はイベントハンドラ(on_*)を定義した場合 ◦ イベントハンドラ→渡したファイルがまた先頭から呼ばれる • 例外その2はfragmentを定義した場合 ◦ @st.fragment内の関数だけ呼ばれる ▪ 関数の外側は呼ばれない 1回目の実行で先頭からpush this!を通り、 2回目の実行で先頭からpushed!を通る。2回目 が4行目から始まるのなら、pushed!はpush this!のあとに出るはず。 イベントハンドラ内 で描画要素を作ると 先に描画される(ア ンチパターン)
  6. AI 10 実行順に描画される(描画の制御①) • 画面構造を定義する機能はない ◦ htmlのようなツリー構造で表現できない • 描画命令を呼んだ(実行)順で上から下に表示される ◦

    先に出した描画要素を後のUIのデータで更新できない(右) ◦ シンプルに書くなら上のUIで絞って下のwidgetに表示さ せる画面構成になる • 表示位置の自由度を上げるには以下の方法がある ◦ on_* で状態を更新する ▪ 簡略版としてst.(*, key=”session_key”)でセッション 値を直接更新させる ◦ st.* の戻り値を使う ◦ st.empty/st.container で遅延生成する ◦ st.rerun() で再描画する ボタンで数値をカウン トアップ ボタンを押した結果が 反映されるのは下のラ ベルだけで、(このま までは)上のラベルに 反映できない session_stateについ ては後述
  7. AI 11 表示位置の自由度を上げる(描画の制御②) • on_* で状態を更新する ◦ 描画と処理を分けられる(◯) • st.empty/st.container

    で遅延生成する ◦ 描画と処理が混ざる(△) • st.* の戻り値を使う ◦ 描画が2ヶ所になる(✕) • st.rerun() で再描画する ◦ 処理が冗長/複雑になる(✕✕) どれも結果は同じ 再描画時には先程のボタン クリックのメッセージは消 費されていてif文がFalseに なるのもややこしい
  8. AI 12 表示位置の自由度を上げる(描画の制御③) • st.(*, key=”session_key”)でセッション値を直接更新 させる ◦ on_changeの関数に変更値を渡せないので、スライ ダなど値を返すUIに対してはこの方法しかなさそう

    ◦ 更新処理を書かなくて良い(◎) このやり方、マルチページ の切り替えでセッションが 消える問題がある(後述) st.sliderの戻り値が今の値になるが、 これを使うとハマる
  9. AI 13 stopとrerunとfragment(描画の制御④) • st.stop() ◦ その場ですべての処理を終了する ▪ 以降の描画要求を捨てる設定を入れ例外で脱出 •

    st.rerun() ◦ その場で処理を停止し、また最初から実行する ▪ ある条件でrerun→別の条件が揃いrerun→…があ るので注意 • st.fragment ◦ 関数にデコレーター@st.fragmentをつけることで、 関数内からの処理で描画したUIを操作すると、この 関数が呼び出される。 ▪ イベントハンドラは関数の前に呼ばれる ▪ streamlit run で指定したファイルは呼ばれない! stopとrerunはgoto的な大域脱出なので、コードの複雑さを 招きやすく使わない方が良い。 fragment()内のボタンを押すとこの関数だけ実行される fragment()外のボタンを押してもfragment()は実行される UIのイベントを流すキューが変わる 関数しか実行されないのでレイアウトをいじる目的では使えない 画面が複雑化した時に処理を減らし認知負荷を下げる効果がある
  10. AI 14 制御機構の細かい癖から気をつけること • 実行順にUIが表示される ◦ シンプルに書きたいなら、画面上部で処理条件、画 面下部で処理結果を出すデザインにする ◦ そうも言ってられない時は、イベントハンドラで処

    理と描画を分ける • UIのどこを触っても全体が実行される ◦ 途中の状態でも処理が流れるので対応が必要 ◦ 処理を軽くする必要がある ▪ セッションやキャッシュ(後述)で重い処理 (CPU/IO)の結果を保持する ▪ fragmentで更新範囲を制限する(副次的)
  11. AI 16 キャッシュ[cache_data, cache_resource](保存機構①) • 関数にデコレーターst.cache_data をつける ◦ 関数への引数をkeyとして、関数の戻り値をpickleで 保持し、複製を返す

    ◦ 普通にデータを読み込んだかのように使える ▪ 通常はこちらを使う • st.cache_resourceというデコレーターもある ◦ 関数への引数をkeyとして、関数の戻り値をそのまま 保持し、実体を返す ◦ 複製不可のオブジェクトに使う ▪ コネクションが典型 • keyはハッシュ化できれば何でもいいので、DBからデー タを取るならSQLクエリをそのまま渡すと楽ができる • Pythonインスタンスで1つなのでWSGIを使うとインス タンスの数だけメモリを使う(△) 一度ロードするとキャッシュが残り、リ ロードしてもアクセスが遅くならない
  12. AI 17 セッション[session_state](保存機構②) • ブラウザタブ単位のdict ◦ BrowserWebSocketHandler.open でオブジェクトがで き、BrowserWebSocketHandler.on_closeでオブジェクト を消しているので、WebSocketのコネクション単位

    • widgetにkey=”session key”を指定すると、 st.session_state[“session key”]に値が入る ◦ 値はon_*イベントハンドラの前に入る ▪ イベントハンドラへ引数を渡せないため、UIの値を渡す 唯一の方法(のはず) ▪ このルールを知らない人には理解できないのが欠点 ◦ ただし、st.navigation でページを切り替えるとkeyが消さ れる⋯これはいただけない ▪ (st.navigationに限らず)widgetがレンダリングされ ないと消される仕様のため ▪ 回避方法は後述 st.sliderでkeyに指定した値はpageを跨ぐことで消える なお、デフォルト値を書き換えるとwidgetが新規に作 られるので、掴んでドラッグができなくなる…
  13. AI 18 URL[query_params](保存機構③) • クエリパラメタ ◦ 書き出すとブラウザのアドレスに反映される ◦ urlを貼り付けて共有できるようになるので、クエリパ ラメタを主、セッションを従で実装できると良さそう

    ▪ 権限管理上もスクショより良い • クエリ↔セッション 変換の支援機能はなさそう ◦ 処理条件が増えるとベタ書きは辛い • こいつもst.navigationのページ遷移で消える ◦ st.Pageでクエリパラメタが渡せない ◦ ページ間で情報を渡したいならセッションに入れる
  14. AI 20 複数ページで状態を保持する(応用①) • widgetのkey指定だとページ切り替えで状態 が消える ◦ イベントハンドラで別名に退避させる ▪ ページIDを採番して保持

    ◦ ページIDと実体が変わったら復帰 page2はurlパラメタも同期させた ここまで定型だとクラス化したくなる なおurlに直に打ち込むとセッションが初期化される 回避にはlocalStorageが良さそうだけどStreamlitに口がない
  15. AI 21 階層セレクター(応用②) • 施設の緯度経度データについて ◦ エリア(区など)を選択したら所在 する施設だけにしたい ◦ エリアも階層化したい(都道府県

    →市区町村) ◦ 未選択なら全部出したい • 実装方式 ◦ selectboxの設定値はon_change で更新してセッションに入れる ◦ 表示データはセッションに入っ たセレクタの値で絞る ◦ UIの設定値はセッションの値を 使う。selectboxの戻り値で何か やろうとしない データ: https://nlftp.mlit.go.jp/