Understanding widget behavior
小部件(如 st.button
、st.selectbox
和 st.text_input
)是 Streamlit 应用的核心。它们是 Streamlit 的交互元素,将用户的信息传递到您的 Python 代码中。小部件非常神奇,通常按照您的期望工作,但在某些情况下可能会有意想不到的行为。了解小部件的不同部分以及事件发生的精确顺序,有助于您实现预期的结果。
本指南涵盖了关于小部件的高级概念。通常,它从较简单的概念开始,逐渐增加复杂性。对于大多数初学者来说,这些细节不需要立即了解。当您想要动态更改小部件或在页面之间保留小部件信息时,这些概念将变得重要。我们建议在阅读本指南之前对会话状态有基本的了解。
- 一个用户的操作不会影响任何其他用户的小部件。
- 一个小部件函数调用返回小部件的当前值,这是一个简单的Python类型。(例如,
st.button
返回一个布尔值。) - 小部件在用户与之交互之前的第一次调用时返回它们的默认值。
- 小部件的身份取决于传递给小部件函数的参数。更改小部件的标签、最小值或最大值、默认值、占位符文本、帮助文本或键将导致其重置。
- 如果您在脚本运行中不调用小部件函数,Streamlit 将删除小部件的信息——包括其在会话状态中的键值对。如果您稍后调用相同的小部件函数,Streamlit 会将其视为一个新小部件。
最后两点(小部件标识和小部件删除)在动态更改小部件或处理多页面应用程序时最为相关。本指南后面将详细介绍这一点:小部件的状态性和小部件的生命周期。
Anatomy of a widget
使用小部件时需要记住四个部分:
- 用户看到的前端组件。
- 后端值或通过
st.session_state
看到的值。 - 用于通过
st.session_state
访问其值的小部件的键。 - 小部件函数返回的值。
Widgets are session dependent
小部件的状态依赖于特定的会话(浏览器连接)。一个用户的操作不会影响任何其他用户的小部件。此外,如果用户打开多个标签页来访问应用程序,每个标签页将是一个独立的会话。在一个标签页中更改小部件不会影响另一个标签页中的相同小部件。
Widgets return simple Python data types
通过st.session_state
看到的小部件的值以及由小部件函数返回的值是简单的Python类型。例如,st.button
返回一个布尔值,并且如果使用键,它将具有相同的布尔值保存在st.session_state
中。第一次调用小部件函数时(在用户与之交互之前),它将返回其默认值。(例如,st.selectbox
默认返回第一个选项。)所有小部件的默认值都是可配置的,除了像st.button
和st.file_uploader
这样的少数特殊例外。
Keys help distinguish widgets and access their values
小部件键有两个用途:
- 区分两个在其他方面相同的小部件。
- 创建一个通过
st.session_state
访问和操作小部件值的方法。
只要有可能,Streamlit 会在前端逐步更新小部件,而不是在每次重新运行时重建它们。这意味着 Streamlit 会根据传递给小部件函数的参数为每个小部件分配一个 ID。小部件的 ID 基于诸如标签、最小值或最大值、默认值、占位符文本、帮助文本和键等参数。小部件出现的页面也会影响其 ID。如果在同一页面上有两个相同类型且参数相同的小部件,您将收到一个 DuplicateWidgetID
错误。在这种情况下,请为这两个小部件分配唯一的键。
Streamlit 无法理解同一页面上两个相同的部件
# This will cause a DuplicateWidgetID error.
st.button("OK")
st.button("OK")
使用键来区分其他相同的部件
st.button("OK", key="privacy")
st.button("OK", key="terms")
Order of operations
当用户与一个小部件交互时,逻辑的顺序是:
- 它在
st.session_state
中的值被更新。 - 回调函数(如果有的话)被执行。
- 页面重新运行,小部件函数返回其新值。
如果回调函数向屏幕写入任何内容,该内容将出现在页面其余部分的上方。回调函数作为脚本重新运行的前缀运行。因此,这意味着通过回调函数写入的任何内容将在用户执行下一个操作时立即消失。通常不应在回调函数内创建其他小部件。
注意
如果回调函数传递了任何args或kwargs,这些参数将在小部件渲染时建立。特别是,如果你想在小部件的回调函数中使用小部件的新值,你不能通过args
参数将该值传递给回调函数;你必须为小部件分配一个键,并在回调函数中使用st.session_state
查找其新值。
Using callback functions with forms
使用带有表单的回调函数需要考虑操作的顺序。
import streamlit as st
if "attendance" not in st.session_state:
st.session_state.attendance = set()
def take_attendance():
if st.session_state.name in st.session_state.attendance:
st.info(f"{st.session_state.name} has already been counted.")
else:
st.session_state.attendance.add(st.session_state.name)
with st.form(key="my_form"):
st.text_input("Name", key="name")
st.form_submit_button("I'm here!", on_click=take_attendance)
Statefulness of widgets
只要小部件的定义参数保持不变并且该小部件在前端持续渲染,那么它将是有状态的并记住用户输入。
Changing parameters of a widget will reset it
如果小部件的任何定义参数发生变化,Streamlit 会将其视为一个新小部件并重置。在这种情况下,手动分配的键和默认值的使用尤为重要。请注意,回调函数、回调参数和关键字参数、标签可见性以及禁用小部件不会影响小部件的身份。
在这个例子中,我们有一个滑块,其最小值和最大值被改变了。尝试与每个滑块交互以改变其值,然后更改最小或最大设置以查看会发生什么。
import streamlit as st
cols = st.columns([2, 1, 2])
minimum = cols[0].number_input("Minimum", 1, 5)
maximum = cols[2].number_input("Maximum", 6, 10, 10)
st.slider("No default, no key", minimum, maximum)
st.slider("No default, with key", minimum, maximum, key="a")
st.slider("With default, no key", minimum, maximum, value=5)
st.slider("With default, with key", minimum, maximum, value=5, key="b")
更新没有默认值的滑块
对于上面的前两个滑块,一旦最小值或最大值发生变化,滑块就会重置为最小值。从Streamlit的角度来看,最小值或最大值的改变使它们成为“新”的小部件,因此当应用程序使用更改后的参数重新运行时,它们会从头开始重新创建。由于没有定义默认值,每个小部件都会重置为其最小值。无论是否有键,这都是相同的,因为它被视为一个新小部件。关于连接到小部件的预先存在的键,有一个微妙之处需要理解。这将在Widget life cycle中进一步解释。
使用默认值更新滑块
对于上面的最后两个滑块,更改最小值或最大值将导致小部件被视为“新”的,因此像以前一样重新创建。由于定义了默认值5,每当最小值或最大值更改时,每个小部件将重置为5。这再次相同(无论是否有键)。
提供了一个解决方案,用于在更改小部件参数时保持状态,稍后会进一步说明。
Widgets do not persist when not continually rendered
如果一个小部件的函数在脚本运行期间未被调用,那么它的任何部分都不会被保留,包括它在st.session_state
中的值。如果一个小部件有一个键并且你离开了那个小部件,它的键和st.session_state
中的关联值将被删除。即使是暂时隐藏一个小部件也会导致它在重新出现时重置;Streamlit会将其视为一个新部件。你可以中断小部件清理过程(在本页末尾描述)或将值保存到另一个键。
将小部件值保存在会话状态中以在页面之间保留它们
如果你想从一个部件导航离开并返回到它,同时保持它的值,可以在st.session_state
中使用一个单独的键来独立于部件保存信息。在这个例子中,一个临时键与一个部件一起使用。临时键使用下划线前缀。因此,"_my_key"
被用作部件键,但数据被复制到"my_key"
以在页面之间保留它。
import streamlit as st
def store_value():
# Copy the value to the permanent key
st.session_state["my_key"] = st.session_state["_my_key"]
# Copy the saved value to the temporary key
st.session_state["_my_key"] = st.session_state["my_key"]
st.number_input("Number of filters", key="_my_key", on_change=store_value)
如果将其功能化以与多个小部件一起使用,它可能看起来像这样:
import streamlit as st
def store_value(key):
st.session_state[key] = st.session_state["_"+key]
def load_value(key):
st.session_state["_"+key] = st.session_state[key]
load_value("my_key")
st.number_input("Number of filters", key="_my_key", on_change=store_value, args=["my_key"])
Widget life cycle
当调用一个小部件函数时,Streamlit 会检查是否已经存在具有相同参数的小部件。如果 Streamlit 认为小部件已经存在,它将重新连接。否则,它将创建一个新的小部件。
如前所述,Streamlit 根据标签、最小值或最大值、默认值、占位符文本、帮助文本和键等参数确定小部件的ID。页面名称也会影响小部件的ID。另一方面,回调函数、回调参数和关键字参数、标签可见性以及禁用小部件不会影响小部件的身份。
Calling a widget function when the widget doesn't already exist
如果你的脚本重新运行时调用了带有更改参数的widget函数,或者调用了上次脚本运行中未使用的widget函数:
- Streamlit 将使用其默认值构建小部件的前端和后端部分。
- 如果小部件已被分配了一个键,Streamlit 将检查该键是否已存在于会话状态中。
a. 如果存在且当前未与另一个小部件关联,Streamlit 将把该键的值分配给小部件。 b. 否则,它将把默认值分配给st.session_state
中的键(创建一个新的键值对或覆盖现有的键值对)。 - 如果回调函数有args或kwargs,它们将在此时间点计算并保存。
- 然后由函数返回小部件的值。
第二步可能会有些棘手。如果你有一个小部件:
st.number_input("Alpha",key="A")
并且你在页面重新运行时将其更改为:
st.number_input("Beta",key="A")
由于标签的更改,Streamlit 会将其视为一个新部件。键 "A"
将被视为标记为 "Alpha"
的部件的一部分,并且不会直接附加到标记为 "Beta"
的新部件上。Streamlit 将销毁 st.session_state.A
并使用默认值重新创建它。
如果一个小部件在创建时附加到一个预先存在的键,并且还手动分配了一个默认值,如果存在差异,您将收到警告。如果您想通过st.session_state
控制小部件的值,请通过st.session_state
初始化小部件的值,并避免使用默认值参数以防止冲突。
Calling a widget function when the widget already exists
在不更改小部件参数的情况下重新运行脚本时:
- Streamlit 将连接到现有的前端和后端部分。
- 如果小部件有一个从
st.session_state
中删除的键,那么Streamlit将使用当前的前端值重新创建该键。(例如,删除一个键不会将小部件恢复为默认值。) - 它将返回小部件的当前值。
Widget clean-up process
当 Streamlit 运行到脚本的末尾时,它将删除内存中未在屏幕上呈现的任何小部件的数据。最重要的是,这意味着 Streamlit 将删除与当前不在屏幕上的小部件相关的 st.session_state
中的所有键值对。
Additional examples
如承诺的那样,让我们来解决在更改页面或修改参数时如何保留小部件的状态。有两种方法可以做到这一点。
- 使用虚拟键在
st.session_state
中复制小部件值,并保护数据不被随小部件一起删除。 - 中断小部件的清理过程。
第一种方法在上面Save widget values in Session State to preserve them between pages中展示
Interrupting the widget clean-up process
要为具有key="my_key"
的小部件保留信息,只需将此添加到每个页面的顶部:
st.session_state.my_key = st.session_state.my_key
当你手动将数据保存到st.session_state
中的某个键时,就清理过程而言,它将与任何小部件分离。如果你从带有某个键"my_key"
的小部件导航离开,并在新页面上将数据保存到st.session_state.my_key
,你将中断小部件的清理过程,并防止键值对被删除或覆盖,如果存在另一个具有相同键的小部件。
Retain statefulness when changing a widget's parameters
这是我们之前示例中更改滑块最小值和最大值的解决方案。此解决方案会中断上述描述的清理过程。
import streamlit as st
# Set default value
if "a" not in st.session_state:
st.session_state.a = 5
cols = st.columns(2)
minimum = cols[0].number_input("Min", 1, 5, key="min")
maximum = cols[1].number_input("Max", 6, 10, 10, key="max")
def update_value():
# Helper function to ensure consistency between widget parameters and value
st.session_state.a = min(st.session_state.a, maximum)
st.session_state.a = max(st.session_state.a, minimum)
# Validate the slider value before rendering
update_value()
st.slider("A", minimum, maximum, key="a")
update_value()
辅助函数实际上在做两件事。表面上,它确保参数值没有不一致的更改,如所述。重要的是,它还在中断小部件的清理过程。当小部件的最小值或最大值发生变化时,Streamlit 会将其视为重新运行时的新小部件。如果没有将值保存到 st.session_state.a
,该值将被丢弃并替换为“新”小部件的默认值。
还有问题吗?
我们的 论坛 充满了有用的信息和Streamlit专家。