👺
Streamlitのイベント発火には on_click を使ってデータの流れを一方向にする
streamlitでボタン押下時に処理する方法の実装方法には2パターンあります。
例としてアルファベットの一覧とページング処理を考えます。
前提:ページング用の共通コード
from dataclasses import dataclass
import pandas as pd
import streamlit as st
@dataclass
class ViewData:
items: list[str]
show_prev: bool
show_next: bool
page: int
@st.cache_data(ttl=60)
def load_alphabets():
return "a b c d e f g h i j k l m n o p q r s t u v w x y z".split()
def init_state() -> None:
st.session_state.setdefault("page", 1)
def show_data() -> ViewData:
page = st.session_state["page"]
per = 5
start, end = (page - 1) * per, page * per
items = load_alphabets()
return ViewData(
items=items[start:end],
show_prev=page > 1,
show_next=end < len(items),
page=page,
)
init_state()
パターン1: st.button の戻り値で判定する
def on_prev_page() -> None:
if st.session_state["page"] > 1:
st.session_state["page"] -= 1
st.rerun()
def on_next_page() -> None:
st.session_state["page"] += 1
st.rerun()
v = show_data()
st.dataframe(v.items)
cols = st.columns(3)
if v.show_prev and cols[0].button("Prev"):
on_prev_page()
if v.show_next and cols[1].button("Next"):
on_next_page()
cols[2].text(f"{v.page} page")
特徴
- st.button は 押下されたときだけ True を返す
- その戻り値を条件分岐に使って、イベントハンドラを呼び出す
- ハンドラ内で st.session_state を更新し、st.rerun() を明示的に呼んでいる
挙動上の注意点
st.button の戻り値を使う場合、イベントハンドラが実行されるのは、スクリプトがその行まで到達したタイミングです。
そのため、
- すでに描画済みの UI(例:st.dataframe)
- イベントハンドラの処理結果に依存する描画
が ハンドラ実行前に存在していると、変更が即座に反映されません。
今回のサンプルでは、
- テーブルを描画
- ボタン押下
- ページ番号を更新
という順序になっているため、
st.rerun() を明示的に呼ばないと「1回遅れてページが切り替わる」ように見えます。
パターン2:st.button の on_click にコールバックを指定する
def on_prev_page() -> None:
if st.session_state["page"] > 1:
st.session_state["page"] -= 1
def on_next_page() -> None:
st.session_state["page"] += 1
v = show_data()
st.dataframe(v.items)
cols = st.columns(3)
if v.show_prev:
cols[0].button("Prev", on_click=on_prev_page)
if v.show_next:
cols[1].button("Next", on_click=on_next_page)
cols[2].text(f"{v.page} page")
特徴
- st.button に コールバック関数を直接指定
- st.rerun() を明示的に呼んでいない
- 状態更新と描画の責務が分離されている
実行順序の違い
on_click を使った場合、ボタン押下時の流れは以下になります。
- 指定されたコールバック関数が先に実行される
- st.session_state が更新される
- Streamlit がスクリプト全体を再実行
- 更新後の状態を使って UI が描画される
そのため、
- 状態更新が描画より先に行われる
- 「1回遅れる」問題が起きない
- st.session_state の依存関係を意識しやすい
というメリットがあります。
まとめ:on_click の方がデータフローを理解しやすい
2つのパターンを比較すると、on_click を使う実装のメリットは下記のメリットがありそうです。
- 状態更新 → 再実行 → 描画という一方向のデータフローになる
- st.session_state と UI 描画の責務を分離しやすい
- st.rerun() を意識せずに実装できる
- 規模が大きくなっても挙動を追いやすい
実際の開発では、ページング処理のような状態更新の制御部分と、ボタンなどの操作コンポーネントが別の場所に配置されるケースも多くあります。
そうした状況でも破綻しにくいという点で、on_click を用いた実装は扱いやすい選択肢だと思います。
Discussion