了解 streamlit 数据流以及如何按顺序提交表单

Understanding streamlit data flow and how to submit form in a sequential way

提问人:Subaru Spirit 提问时间:2/14/2023 最后编辑:Subaru Spirit 更新时间:2/16/2023 访问量:865

问:

下面是一个简单可重现的示例,它以简单的形式说明问题。您可以跳转到代码和预期行为,因为问题描述可能很长。

主要概念

一个列表中存储了 3 个数据帧,侧边栏上的窗体显示相关数据帧中的 和。当用户单击该按钮时,将保存 和 text_input 中的信息(在本例中,它们基本上被打印在侧边栏的顶部)。supplier_namepo_numberNextsupplier_namepo_number
enter image description here

问题

当用户不更改text_input内的任何内容时,此应用运行良好,但如果用户更改了某些内容,则会破坏应用。例如,请参阅下图,当我更改为 时,保存的信息不是来自第一个数据帧。po_numbersomethingrandomsomethingrandomp123

enter image description here

此外,如果来自下一个 DataFrame 的信息与第一个 DataFrame 相同,则text_input内更改的值对于下一次显示将保持不变。例如,由于第一个和第二个数据帧的供应商名称都是,如果我将供应商名称更改为 ,然后单击下一步,则 仍然在第二个数据帧上,而第二个数据帧的supplier_name应该是 。但是,如果下一个数据帧的供应商名称发生更改,则其中的信息将发生更改。S1S10supplier_nameS10S1text_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 中提取,并重复。

Python 流光

评论


答:

0赞 Caroline Frasca 2/16/2023 #1

我不确定我是否完全理解您要完成的任务,但是在用户通过文本输入小部件更新名称后,您似乎永远不会更新供应商名称。list1

1赞 ggorlen 2/16/2023 #2

我不完全确定你要做什么,但经过一番折腾,我能够实现这种顺序表单提交处理的唯一方法是使用 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.selectionsdfs

# ...
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_namepo_numberdf.columnsinitialize_state

评论

0赞 Subaru Spirit 2/17/2023
谢谢你的回答。几天前我也发现了experiment_run,你是对的,这是一个可能会被删除的功能。我很困惑为什么它需要单击按钮两次才能提取信息,因为当单击按钮时,应用程序没有从上到下刷新。绕过experiemnt_run的一种方法是使用on_click然后将密钥分配给小部件,我还没有尝试过,稍后会在这里更新。
0赞 Subaru Spirit 2/17/2023
目标基本上是首先读取一批文档,所有这些文档(假设每个文档都是一个数据帧)都存储在一个列表中。然后侧边栏会逐个读取每个文档中的内容,但如果出现问题,用户可以更正侧边栏。用户单击下一步后,这些信息将保存到 Excel 电子表格中,用户将转到列表中的下一个文档。实际文档不会被修改,但这些信息将被保存。希望现在更有意义。
0赞 ggorlen 2/19/2023
感谢您的回复。我尝试使用处理程序,但无法让它工作,这就是我求助于.我还想在可能的情况下重新审视这个问题,并尝试让它与回调一起工作,但如果你打败了我,请随时发表评论和答案。on_clickexperimental_rerun()