1. はじめに
Pythonで手軽にWebアプリが実装できるStreamlitですが、動的なユーザの入力を行うためには工夫が必要です。本記事ではStreamlitでの動的なユーザ入力の実装方法について説明していこうと思います。
2. Streamlitとは
StreamlitはPythonで簡単にWebアプリケーションを作成できるオープンソースのライブラリです。
詳細は以下の記事で説明しています。
3. 動的なユーザ入力の実装方法
次のような足し算アプリをサンプルとして説明していきます。
- デフォルトでは2つの数値入力欄が存在する
- 入力欄にユーザは数字を入力し、"合計"ボタンをクリックすると全テキストボックスに入力された数字の合計が表示される
- ユーザは"追加"ボタンを使って入力欄を自由に追加することができる
- ユーザは"削除"ボタンを使って入力欄を自由に削除することができる
- ただし、入力欄の数は2より少なくならない
3.1. ベースの実装
まずは入力欄の数を2つに固定した場合のコードをベースコードとして書いていきます。ベースコードは以下になります。
import streamlit as st
st.title("足し算アプリ")
# st.number_inputを2つ用意
value_1 = st.number_input("数字1", value=0)
value_2 = st.number_input("数字2", value=0)
# 合計ボタン
if st.button("合計"):
# 合計ボタンが押されたら数値を合計し表示
total = value_1 + value_2
st.write(f"合計値: {total}")
ここから追加と削除ができるように修正していきます。
3.2. 入力欄の追加
入力欄の追加にはst.session_state
とst.button()
の引数on_click
を使います。
st.session_state
は画面の再表示の際に再表示前の値を保持することのできる機能です。
Streamlitはボタンを押した際に画面の更新が行われます。st.button()
の引数on_click
にコールバック関数を指定することでボタンの押下と画面の更新の間に処理を挟むことができます。
こちらの参考サイトではより詳細に解説をしてくださっています。
Streamlitではst.rerun()
を使って任意のタイミングで画面を更新することが可能ですが、副作用が生じる可能性があるため、コールバック関数が設定できる場合はコールバック関数を利用することが公式では推奨されています。
https://docs.streamlit.io/library/api-reference/control-flow/st.rerun
import streamlit as st
# 追加用コールバック関数
def add_number_input():
# 初期値0のnumber_inputを増やす
st.session_state["values"].append(0)
st.title("足し算アプリ")
# session_stateで入力された数値を管理
if "values" not in st.session_state:
# 初期化。初期のnumber_inputは2つで、初期値は0
st.session_state["values"] = [0, 0]
# st.number_inputを用意
for i, number in enumerate(st.session_state["values"]):
st.number_input(
# widgetごとに一意のKeyを用意
key = f"number_input_{str(i+1)}",
label = f"数字{i+1}",
value=number
)
# 追加ボタン
add_button = st.button("追加", on_click=add_number_input)
# 合計ボタン
if st.button("合計"):
total = 0
for i, number in enumerate(st.session_state["values"]):
# session_stateにnumber_inputのKeyで入力値が取得可能
total += st.session_state[f"number_input_{str(i+1)}"]
st.write(f"合計値: {total}")
3.3. 入力欄の削除
入力欄の削除にもst.session_state
とコールバック関数を使います。
現在入力されている値を画面更新後に表示するnumber_inut
の初期値に設定することで、入力欄を削除しても他の入力された数値を保持することができます。また、削除ボタンもそれぞれ一意のKeyを設定する必要があるため、Keyに番号を振っています。
また、Streamlitでは一意のウィジェットのKeyとそのウィジェットの値がst.session_state
に保持されます。そのため、削除したnumber_input
と同じKeyのウィジェットを削除後に作成すると削除前の値が入ってしまいます。なので、ウィジェットは削除の前後で異なるKeyを持たなければなりません。今回は削除ボタンを押した回数をst.session_state
で保持し、その回数をKeyに利用することで一意のKeyを新しく生成しています。
import streamlit as st
# 追加用コールバック関数
def add_number_input():
# 初期値0のnumber_inputを増やす
st.session_state["values"].append(0)
# 削除用コールバック関数
def delete_number_input(target_no):
# number_inputは必ず2つ以上
if len(st.session_state["values"]) > 2:
# 新しい初期値のリスト
new_values = []
for j in range(len(st.session_state["values"])):
# 削除対象のnumber_inputはスキップ
if j != target_no:
# 初期値に現在の値を追加
new_values.append(st.session_state[f"number_input_{str(j+1)}_{str(st.session_state['count'])}"])
# st.session_stateに不要な要素が残るので削除
del st.session_state[f"number_input_{str(j+1)}_{str(st.session_state['count'])}"]
# 初期値のリストを更新
st.session_state["values"] = new_values
# 削除回数をインクリメント
st.session_state["count"] += 1
st.title("足し算アプリ")
# session_stateで入力された数値を管理
if "values" not in st.session_state:
# 初期化。初期のnumber_inputは2つで、初期値は0
st.session_state["values"] = [0, 0]
# session_stateで削除ボタンが押された回数を管理
if "count" not in st.session_state:
# 初期化
st.session_state["count"] = 0
# st.number_inputを用意
for i, number in enumerate(st.session_state["values"]):
st.number_input(
# widgetごとに一意のKeyを用意
key = f"number_input_{str(i+1)}_{str(st.session_state['count'])}",
label = f"数字{i+1}",
value=number
)
# 削除ボタン、複数個あるため一意のKeyを設定
delete_button = st.button("削除", key=f"delete_{str(i+1)}", on_click=delete_number_input, args=(i, ))
# 追加ボタン
add_button = st.button("追加", on_click=add_number_input)
# 合計ボタン
if st.button("合計"):
total = 0
for i, number in enumerate(st.session_state["values"]):
# session_stateのnumber_inputのKeyで入力値が取得可能
total += st.session_state[f"number_input_{str(i+1)}_{str(st.session_state['count'])}"]
st.write(f"合計値: {total}")
4. まとめ
Streamlitで動的なユーザの入力を実装する方法を紹介しました。
ポイントは以下の3つになります。
-
st.session_state
で入力欄の個数と初期値を保持する - コールバック関数を利用して画面更新の前に入力欄増減の処理を入れる
- 画面更新前後でウィジェットには異なる一意のKeyを付与する
筆者も動的なユーザの入力の実装で困った経験があるので、この記事が誰かの助けになれば嬉しいです。