问题描述
我有两个输入字段,这些字段允许用户通过
进行选择- ID,在输入的数字中
- (排序的)名称,在选择框中
更改一个输入字段应该更新另一个输入字段,以使其保持同步。
您如何通过streamlit实现这种行为?
到目前为止我尝试过的事情
已选择ID->更新名称选择框:
users = [(1,'Jim'),(2,(3,'Jane')]
users.sort(key=lambda user: user[1]) # sort by name
selected_id = st.sidebar.number_input('ID',value=1)
options = ['%s (%d)' % (name,id) for id,name in users]
index = [i for i,user in enumerate(users) if user[0] == selected_id][0]
selected_option = st.sidebar.selectbox('Name',options,index)
选择名称->输入更新ID编号(使用st.empty()
):
users = [(1,'Jane')]
users.sort(key=lambda user: user[1]) # sort by name
id_input = st.sidebar.empty()
options = ['%s (%d)' % (name,name in users]
selected_option = st.sidebar.selectbox('Name',options)
# e.g. get 2 from "Jim (2)"
id = int(re.match(r'\w+ \((\d+)\)',selected_option).group(1))
selected_id = id_input.number_input('ID',value=id)
解决方法
要保持小部件同步,需要解决两个问题:
- 我们需要能够判断任一小部件何时导致当前选择发生变化;和
- 我们需要在脚本结束时更新两个小部件的状态,以便在重新运行脚本以进行视觉更新时浏览器保持新值。
对于 (1),如果不引入某种持久状态,就没有办法做到这一点。没有办法在脚本运行之间存储当前选择,我们只能将两个小部件的值相互比较并与默认值进行比较。一旦widgets被改变,这会导致问题:例如,如果默认值为1,数字输入的值为2,选择框的值为3,我们无法分辨是数字输入还是选择框最近更改的(因此将哪个更新为最新值)。
对于 (2),只要选择更改,就可以使用占位符并刷新小部件。重要的是,如果选择没有改变,小部件应该不刷新,否则我们会得到 DuplicateWidgetID
错误(因为小部件的内容也不会改变,它们将具有生成的密钥相同)。
以下是一些代码,展示了处理这两个问题并在最后捕获用户选择的一种方法。请注意,以这种方式使用 @st.cache
将在多个浏览器会话中保留一个全局选择,并允许任何人通过 Streamlit 菜单 -> 'Clear cache' 清除选择,如果有多个用户,这可能是一个问题同时访问脚本。
import re
import streamlit as st
# Simple persistent state: The dictionary returned by `get_state()` will be
# persistent across browser sessions.
@st.cache(allow_output_mutation=True)
def get_state():
return {}
# The actual creation of the widgets is done in this function.
# Whenever the selection changes,this function is also used to refresh the input
# widgets so that they reflect their new state in the browser when the script is re-run
# to get visual updates.
def display_widgets():
users = [(1,"Jim"),(2,(3,"Jane")]
users.sort(key=lambda user: user[1]) # sort by name
options = ["%s (%d)" % (name,id) for id,name in users]
index = [i for i,user in enumerate(users) if user[0] == state["selection"]][0]
return (
number_placeholder.number_input(
"ID",value=state["selection"],min_value=1,max_value=3,),option_placeholder.selectbox("Name",options,index),)
state = get_state()
# Set to the default selection
if "selection" not in state:
state["selection"] = 1
# Initial layout
number_placeholder = st.sidebar.empty()
option_placeholder = st.sidebar.empty()
# Grab input and detect changes
selected_number,selected_option = display_widgets()
input_changed = False
if selected_number != state["selection"] and not input_changed:
# Number changed
state["selection"] = selected_number
input_changed = True
display_widgets()
selected_option_id = int(re.match(r"\w+ \((\d+)\)",selected_option).group(1))
if selected_option_id != state["selection"] and not input_changed:
# Selectbox changed
state["selection"] = selected_option_id
input_changed = True
display_widgets()
st.write(f"The selected ID was: {state['selection']}")