Streamlitでアプリケーションを開発する際、セレクトボックスなどの選択ウィジェットで選択した値をst.session_state内変数として保持しておきたい場合があります。
このst.session_stateはマルチページのアプリケーション開発においても対応しており、ページ遷移があったとしても変数の値を保持できます。
しかし、セレクトボックスなどで「初期値」をもたせたうえ、一度ユーザーが選択した内容をsession_stateに保存、さらにセレクトボックスに継続的に選択済の選択肢として反映させたい、などという場合には一工夫が必要となります。
目次:
まずは前提としてStreamlitのウィジェットのkeyにおける動作と、マルチページにおけるsession_stateの扱い方について解説します。
前提1: ウィジェット内で設定したkeyの情報で、自動的にst.session_state[’key_name’]としてセッション情報が作成される
まず前提の注意事項として、st.selectboxやst.text_inputなどのインプットウィジェットで指定可能なkeyというパラメータについて。
keyパラメータは「そのウィジェット固有のユニークな名称」として指定でき、ウィジェットに対するIDのような形で指定することが可能です。keyパラメータに名称を指定しなかった場合は、自動的に名称が生成されて割り振られます。
このkeyパラメータで指定した名称で、st.session_stateの辞書にインプットウィジェットで選択した値が入るように自動的に設定されます。
Session State and Widget State association Every widget with a key is automatically added to Session State: st.text_input("Your name", key="name") This exists now: st.session_state.name
Session State - Streamlit Docs
具体的なケースだと、たとえば下記のような挙動をします。
# テキストインプットのウィジェット
st.text_input(label="Test", key="text_widget") # UI上で「テストです」と入力したとする
# st.session_state辞書で自動的に生成・保持される値
st.session_state['text_widget'] = "テストです"
このように、keyパラメータを使用することで「ウィジェットとsession_stateを自動的に紐づけ」させることが可能となります。
通常のtext_inputやtext_areaなどのテキスト入力系ウィジェットの場合、このkeyパラメータによるsession_stateとの自動的な紐づけを利用すると便利でしょう。
しかし、セレクトボックス等のデフォルト値があるウィジェットの場合で、かつマルチページのアプリケーションを開発する場合、この自動的な紐づけが意図しない挙動を発生させてしまうことがあります。
前提2: マルチページの場合、すべてのページでsession_stateの初期値チェックが必要
session_stateを利用する場合、利用する変数名が存在しない場合に初期値を作ってあげる「初期化」作業が必要となります。
import streamlit as st
# Check if 'key' already exists in session_state
# If not, then initialize it
if 'key' not in st.session_state:
st.session_state['key'] = 'value'
# Session State also supports the attribute based syntax
if 'key' not in st.session_state:
st.session_state.key = 'value'
Add statefulness to apps - Streamlit Docs
この初期化作業は、マルチページの場合は作成した「すべてのページ」で必要となるようです。
たとえば、下記のように関数化して呼び出してあげると良いでしょう。
def initialize_session_state(defaults: dict) -> None:
# Initialize session state with preset values
if 'something_key_name' not in st.session_state:
st.session_state['something_key_name'] = "テスト名称"
質問フォーラムでも、いくつかの投稿への回答で口酸っぱく「すべてのページでsession_stateの初期化を行え」と言われていました。
Did you make sure that st.session_state was initialized correctly on each page? Checking that the keys, like authentication_status, are used consistently across your app.
Streamlit session state deleted when switch page - Using Streamlit - Streamlit
なぜ全てのページでこの初期化作業が必要になるのかというと、「Streamlitはファイル内で使用されなかった(呼び出されなかった)session_stateの値を自動的に削除してしまうから」とのこと。
Session State is preserved between pages, but there is a caveat when you specifically mean a key-value pair associated to a widget. Streamlit deletes key-value pairs for widgets when they are not rendered on an app run. In particular, it gets deleted at the end of the app run.
Session state is not preserved when navigating pages - Using Streamlit - Streamlit
そのため、if not 値〜 の初期化構文で一度はsession_state内の値を呼び出してあげる必要があるのでしょう。
ただし、後述するセレクトボックスにおけるkey指定で自動生成されるsession_stateの値においてはこの限りでなく、特に初期化しなくてもページ遷移があろうと値が保持されていました。
初期選択値があるセレクトボックスのようなウィジェットの場合、callback関数を使ってsession_stateの値を更新させる
st.text_inputやst.text_areaなどのテキスト入力系ウィジェットでは、初期選択値は空としてそのまま利用する場合が多いです。プレースホルダーとしてサンプルテキストを設置することで対処することがほとんどでしょう。
このような場合、keyパラメータでユニークな名称を指定してあげるだけでOKです。指定してあげた名称でsession_stateに値が保持されます。
画面を切り替えた描画の際に、セレクトボックスの初期値とsession_stateの内容に「ズレ」が生じる
セレクトボックスに初期値を持たせるようなウィジェットとする場合、ウィジェットでデフォルトとして設定した値で、毎回session_stateの値が上書きされてしまいます。
# 選択肢リストと初期値
options_list = ["Initial", "Changed"]
default = "Initial"
# セッション状態をウィジェットとしてのデフォルト値としたセレクトボックス
st.selectbox(
label="選択:",
options=options_list,
key="my_select_box",
index=options_list.index(default) #デフォルト選択値を"Initial"に
)
# 画面描画時のセッションの値
st.session_state['my_select_box'] = "Initial"
# UIで変更した場合、同画面上でのセッションの値
st.session_state['my_select_box'] = "Changed"
# UIで変更した後、画面遷移を経て同画面に戻ってきたときのセッションの値
st.session_state['my_select_box'] = "Initial" #描画時に初期値で書き換えられる
上記のようなコードの場合、デフォルト選択肢としてInitialが選択された状態で画面に描画され、選択した値はst.session_state[’my_select_box’]としてセッションに保存されますが、その後、画面を切り替えるたびに描画時に初期選択肢で値が書き換えられてしまいます。
keyで保持する値とは別にsession_state内に別名で変数を作成し、callback関数で上書きする
コールバック関数とは、セレクトボックスなど、「ウィジェットにおいてユーザーの操作がなされたときに、即時実行される関数」です。画面の再描画よりも早いタイミングで実行されます。
このコールバック関数を利用することで、画面描画によってデフォルト選択肢に値が書き換えられてしまう前に、ユーザーが選択した値でセッション変数を上書きすることができます。
コールバック関数は、on_changeやon_clickパラメータによって指定が可能です。
options_list = ["Initial", "Changed"]
# session_stateの初期化(保存用セッション変数)
if 'session_value' not in st.session_state:
st.session_state['session_value'] = "Initial"
# セレクトボックス用に別途、セッション変数を作成(セレクトボックス用変数)
# keyで生成しているので省略可能かも?
if 'selected_value' not in st.session_state:
# セレクトボックス用変数の初期値には、セッション変数の値を反映させる
st.session_state['selected_value'] = st.session_state['session_value']
# セレクトボックス用変数の値を更新させるコールバック関数
def update_state():
# セレクトボックスで選んだ値が保存用セッションの値と異なる場合、保存用セッション変数を更新
# st.session_state["selected_value"]の値は、セレクトボックスウィジェットにおけるkey紐づけによって選択時に自動更新される
if st.session_state["selected_value"] != st.session_state["session_value"]:
st.session_state["session_value"] = st.session_state["selected_value"]
# セレクトボックス
st.selectbox(label="選択:",
options=options_list,
# セレクトボックス用変数をセッション変数として紐づけ
key="scene_model_select",
# 初期選択済の選択肢は保存用セッション変数の値を反映
index=model_list.index(st.session_state['session_value']),
# コールバック関数
on_change=update_state
)
なお、コールバック関数に対して値を渡したいときには、別途args、またはkwargsというパラメータを指定することで可能となります。
また前述のように、keyの値で生成した以外のセッション変数はすべてのページで初期化作業が必要となりますので、初期化作業によるセッション変数の値チェックはすべてのページで実施してください。
Category: 開発・プログラミング | エンジニアリング
Tags: Streamlit
おすすめ記事: Streamlit Community CloudでWebアプリを公開する際の準備事項