提问人:Subaru Spirit 提问时间:2/14/2023 最后编辑:Subaru Spirit 更新时间:2/16/2023 访问量:865
了解 streamlit 数据流以及如何按顺序提交表单
Understanding streamlit data flow and how to submit form in a sequential way
问:
下面是一个简单可重现的示例,它以简单的形式说明问题。您可以跳转到代码和预期行为,因为问题描述可能很长。
主要概念
一个列表中存储了 3 个数据帧,侧边栏上的窗体显示相关数据帧中的 和。当用户单击该按钮时,将保存 和 text_input 中的信息(在本例中,它们基本上被打印在侧边栏的顶部)。supplier_name
po_number
Next
supplier_name
po_number
问题
当用户不更改text_input内的任何内容时,此应用运行良好,但如果用户更改了某些内容,则会破坏应用。例如,请参阅下图,当我更改为 时,保存的信息不是来自第一个数据帧。po_number
somethingrandom
somethingrandom
p123
此外,如果来自下一个 DataFrame 的信息与第一个 DataFrame 相同,则text_input内更改的值对于下一次显示将保持不变。例如,由于第一个和第二个数据帧的供应商名称都是,如果我将供应商名称更改为 ,然后单击下一步,则 仍然在第二个数据帧上,而第二个数据帧的supplier_name应该是 。但是,如果下一个数据帧的供应商名称发生更改,则其中的信息将发生更改。S1
S10
supplier_name
S10
S1
text_input
理由
如果您难以理解我为什么要这样做,那么最初的用途是侧边栏输入区域从每个 PDF 中提取信息,然后当用户确认信息都正确时,他们单击下一步查看下一个 PDF。但是如果出了什么问题,他们可以更改text_input内的信息,然后单击下一步,并且将记录更改值的信息,对于下一个pdf,提取的信息应该反映下一个pdf是什么。我在 R shiny 中非常简单地执行了此操作,但无法弄清楚数据流在 streamlit 中是如何工作的,请帮忙。
可重现的示例
import streamlit as st
import pandas as pd
# 3 dataframes that are stored in a list
data1 = {
"supplier_name": ["S1"],
"po_number": ["P123"],
}
data2 = {
"supplier_name": ["S1"],
"po_number": ["P124"],
}
data3 = {
"supplier_name": ["S2"],
"po_number": ["P125"],
}
df1 = pd.DataFrame(data1)
df2 = pd.DataFrame(data2)
df3 = pd.DataFrame(data3)
list1 = [df1, df2, df3]
# initiate a page session state, every time next button is clicked
# it will go to the next dataframe in the list
if 'page' not in st.session_state:
st.session_state.page = 0
def next_page():
st.sidebar.write(f"Submitted! supplier_name: {supplier_name} po_number: {po_number}")
st.session_state.page += 1
supplier_name_value = list1[st.session_state.page]["supplier_name"][0]
po_number_value = list1[st.session_state.page]["po_number"][0]
# main area
list1[st.session_state.page]
# sidebar form
with st.sidebar.form("form"):
supplier_name = st.text_input(label="Supplier Name", value=supplier_name_value)
po_number = st.text_input(label="PO Number", value=po_number_value)
next_button = st.form_submit_button("Next", on_click=next_page)
预期行为
数据框的信息被提取到侧边栏输入区域。如果用户愿意,可以更改输入,然后单击下一步,输入区域内的值将被保存。当它转到下一个 DataFrame 时,文本输入中的值将被刷新以从下一个 DataFrame 中提取,并重复。
答:
我不确定我是否完全理解您要完成的任务,但是在用户通过文本输入小部件更新名称后,您似乎永远不会更新供应商名称。list1
我不完全确定你要做什么,但经过一番折腾,我能够实现这种顺序表单提交处理的唯一方法是使用 st.experimental_rerun()。
我讨厌诉诸于它,因为它可能随时被删除,所以希望有更好的方法。
如果没有,表单需要两次提交才能实际更新状态。我无法找到一种“正确”的方法来实现即时更新以支持预期的行为。experimental_rerun()
这是我的尝试:
import pandas as pd # 1.5.1
import streamlit as st # 1.18.1
def initialize_state():
data = [
{
"supplier_name": ["S1"],
"po_number": ["P123"],
},
{
"supplier_name": ["S1"],
"po_number": ["P124"],
},
{
"supplier_name": ["S2"],
"po_number": ["P125"],
},
]
state.dfs = state.get("dfs", [pd.DataFrame(x) for x in data])
first_vals = [{x: df[x][0] for x in df.columns} for df in state.dfs]
state.selections = state.get("selections", first_vals)
state.pages_expanded = state.get("pages_expanded", 0)
state.current_page = state.get("current_page", 0)
state.just_modified_page = state.get("just_modified_page", -1)
def handle_submit(i):
st.session_state.selections[i] = {
"supplier_name": state.new_supplier_name,
"po_number": state.new_po_number,
}
state.current_page = i
state.just_modified_page = i
if i < len(state.dfs) - 1 and state.pages_expanded == i:
state.pages_expanded += 1
st.experimental_rerun()
def render_form(i):
with st.sidebar.form(key=f"form-{i}"):
supplier_name = state.selections[i]["supplier_name"]
po_number = state.selections[i]["po_number"]
if i == state.just_modified_page:
st.sidebar.write(
f"Submitted! supplier_name: {supplier_name} "
f"po_number: {po_number}"
)
state.just_modified_page = -1
state.new_supplier_name = st.text_input(
label="Supplier Name",
value=supplier_name,
)
state.new_po_number = st.text_input(
label="PO Number",
value=po_number,
)
if st.form_submit_button("Next"):
handle_submit(i)
state = st.session_state
initialize_state()
for i in range(state.pages_expanded + 1):
render_form(i)
# debug
st.write("state.pages_expanded", state.pages_expanded)
st.write("state.current_page", state.current_page)
st.write("state.just_modified_page", state.just_modified_page)
st.write("state.dfs[state.current_page]", state.dfs[state.current_page])
st.write("state.selections", state.selections)
我假设您想跟踪用户的选择,但实际上不修改数据帧。如果您确实要修改数据帧,那就更简单了:替换为 按索引和列写入的实际内容:state.selections
dfs
# ...
def handle_submit(i):
st.session_state.dfs[i]["supplier_name"] = state.new_supplier_name,
st.session_state.dfs[i]["po_number"] = state.new_po_number,
#st.session_state.selections[i] = {
# "supplier_name": state.new_supplier_name,
# "po_number": state.new_po_number,
#}
# ...
def render_form(i):
with st.sidebar.form(key=f"form-{i}"):
supplier_name = state.dfs[i]["supplier_name"][0]
po_number = state.dfs[i]["po_number"][0]
#supplier_name = state.selections[i]["supplier_name"]
#po_number = state.selections[i]["po_number"]
# ...
现在,可以使它 100% 动态化,但我进行了硬编码,并避免了您可能不需要的过早泛化。如果确实要泛化,请在整个代码中使用 like does。supplier_name
po_number
df.columns
initialize_state
评论
on_click
experimental_rerun()
评论