Add statefulness to apps

我们将浏览器标签中访问Streamlit应用程序定义为会话。对于每个连接到Streamlit服务器的浏览器标签,都会创建一个新的会话。每次与应用程序交互时,Streamlit都会从头到尾重新运行您的脚本。每次重新运行都在一个空白的状态下进行:运行之间不共享任何变量。

会话状态是一种在每次用户会话的重新运行之间共享变量的方式。除了能够存储和持久化状态外,Streamlit 还提供了使用回调函数操作状态的能力。会话状态在多页面应用中的各个页面之间也会持久化。

在本指南中,我们将通过构建一个有状态的计数器应用程序来说明会话状态回调的使用。

有关会话状态和回调API的详细信息,请参阅我们的会话状态API参考指南

此外,查看由Streamlit开发者倡导者Marisa Smith博士提供的这个会话状态基础教程视频以开始学习:

让我们把我们的脚本称为counter.py。它初始化了一个count变量,并有一个按钮来增加存储在count变量中的值:

import streamlit as st st.title('Counter Example') count = 0 increment = st.button('Increment') if increment: count += 1 st.write('Count = ', count)

无论我们在上面的应用中按下多少次增加按钮,count始终保持在1。让我们理解一下原因:

  • 每次我们按下增加按钮时,Streamlit 会从头到尾重新运行counter.py,并且在每次运行时,count都会被初始化为0
  • 按下增加后,0加1,因此无论我们按下多少次增加count=1

正如我们稍后将看到的,我们可以通过将count存储为会话状态变量来避免这个问题。通过这样做,我们向Streamlit表明,它应该在应用程序重新运行时维护存储在会话状态变量中的值。

让我们了解更多关于使用会话状态的API。

会话状态API遵循基于字段的API,这与Python字典非常相似:

import streamlit as st # Check if 'key' already exists in session_state # If not, then initialize it if 'key' not in st.session_state: st.session_state['key'] = 'value' # Session State also supports the attribute based syntax if 'key' not in st.session_state: st.session_state.key = 'value'

通过将项目传递给st.write来读取会话状态中项目的值:

import streamlit as st if 'key' not in st.session_state: st.session_state['key'] = 'value' # Reads st.write(st.session_state.key) # Outputs: value

通过为会话状态中的项目赋值来更新它:

import streamlit as st if 'key' not in st.session_state: st.session_state['key'] = 'value' # Updates st.session_state.key = 'value2' # Attribute API st.session_state['key'] = 'value2' # Dictionary like API

如果访问未初始化的变量,Streamlit 会抛出异常:

import streamlit as st st.write(st.session_state['value']) # Throws an exception!
state-uninitialized-exception

现在让我们看几个例子,这些例子展示了如何向我们的计数器应用程序添加会话状态。

现在我们已经掌握了Session State API,让我们更新我们的计数器应用程序以使用Session State:

import streamlit as st st.title('Counter Example') if 'count' not in st.session_state: st.session_state.count = 0 increment = st.button('Increment') if increment: st.session_state.count += 1 st.write('Count = ', st.session_state.count)

正如你在上面的例子中看到的,每次按下增加按钮都会更新count

现在我们已经使用会话状态构建了一个基本的计数器应用程序,让我们继续做一些更复杂的事情。下一个示例使用带有会话状态的回调。

回调函数: 回调函数是一个Python函数,当输入小部件发生变化时会被调用。回调函数可以通过使用参数on_change(或on_click)、argskwargs与小部件一起使用。完整的回调函数API可以在我们的会话状态API参考指南中找到。

import streamlit as st st.title('Counter Example using Callbacks') if 'count' not in st.session_state: st.session_state.count = 0 def increment_counter(): st.session_state.count += 1 st.button('Increment', on_click=increment_counter) st.write('Count = ', st.session_state.count)

现在,每次按下增加按钮都会通过调用increment_counter()函数来更新计数。

回调也支持在小部件中使用args参数传递参数:

import streamlit as st st.title('Counter Example using Callbacks with args') if 'count' not in st.session_state: st.session_state.count = 0 increment_value = st.number_input('Enter a value', value=0, step=1) def increment_counter(increment_value): st.session_state.count += increment_value increment = st.button('Increment', on_click=increment_counter, args=(increment_value, )) st.write('Count = ', st.session_state.count)

此外,我们还可以在小部件中使用kwargs参数将命名参数传递给回调函数,如下所示:

import streamlit as st st.title('Counter Example using Callbacks with kwargs') if 'count' not in st.session_state: st.session_state.count = 0 def increment_counter(increment_value=0): st.session_state.count += increment_value def decrement_counter(decrement_value=0): st.session_state.count -= decrement_value st.button('Increment', on_click=increment_counter, kwargs=dict(increment_value=5)) st.button('Decrement', on_click=decrement_counter, kwargs=dict(decrement_value=1)) st.write('Count = ', st.session_state.count)

假设我们现在不仅想要增加count,还想要存储它最后一次更新的时间。我们通过使用回调和st.form来演示如何做到这一点:

import streamlit as st import datetime st.title('Counter Example') if 'count' not in st.session_state: st.session_state.count = 0 st.session_state.last_updated = datetime.time(0,0) def update_counter(): st.session_state.count += st.session_state.increment_value st.session_state.last_updated = st.session_state.update_time with st.form(key='my_form'): st.time_input(label='Enter the time', value=datetime.datetime.now().time(), key='update_time') st.number_input('Enter a value', value=0, step=1, key='increment_value') submit = st.form_submit_button(label='Update', on_click=update_counter) st.write('Current Count = ', st.session_state.count) st.write('Last Updated = ', st.session_state.last_updated)

会话状态提供了在重新运行之间存储变量的功能。小部件状态(即小部件的值)也存储在会话中。

为了简化,我们将这些信息统一在一个地方,即会话状态。这个便利功能使得在应用程序代码的任何地方读取或写入小部件的状态变得非常容易。会话状态变量使用key参数来反映小部件的值。

我们通过以下示例来说明这一点。假设我们有一个应用程序,其中有一个滑块用于表示摄氏温度。我们可以使用Session State API来设置获取温度小部件的值,如下所示:

import streamlit as st if "celsius" not in st.session_state: # set the initial default value of the slider widget st.session_state.celsius = 50.0 st.slider( "Temperature in Celsius", min_value=-100.0, max_value=100.0, key="celsius" ) # This will get the value of the slider widget st.write(st.session_state.celsius)

使用会话状态API设置小部件值存在限制。

priority_high

重要

Streamlit 不允许通过Session State API为st.buttonst.file_uploader设置小部件的值。

以下示例在尝试通过会话状态API设置st.button的状态时,将引发StreamlitAPIException

import streamlit as st if 'my_button' not in st.session_state: st.session_state.my_button = True # Streamlit will raise an Exception on trying to set the state of button st.button('Submit', key='my_button')
state-button-exception

序列化是指将对象或数据结构转换为可以持久化和共享的格式,并允许您恢复数据的原始结构的过程。Python的内置pickle模块将Python对象序列化为字节流(“pickling”),并将流反序列化为对象(“unpickling”)。

默认情况下,Streamlit的Session State允许您在会话期间持久化任何Python对象,无论该对象是否可pickle序列化。此属性使您可以存储Python基本类型,如整数、浮点数、复数和布尔值,数据框,甚至函数返回的lambdas。然而,某些执行环境可能要求序列化Session State中的所有数据,因此在开发期间检测不兼容性,或当执行环境将来停止支持时,可能会很有用。

为此,Streamlit 提供了一个 runner.enforceSerializableSessionState 配置选项,当设置为 true 时,只允许在 Session State 中使用可 pickle 序列化的对象。要启用此选项,可以创建一个全局或项目配置文件,内容如下,或者将其用作命令行标志:

# .streamlit/config.toml [runner] enforceSerializableSessionState = true

所谓“pickle可序列化”,我们指的是调用pickle.dumps(obj)不应引发PicklingError异常。当启用配置选项时,向会话状态添加不可序列化的数据应导致异常。例如,

import streamlit as st def unserializable_data(): return lambda x: x #👇 results in an exception when enforceSerializableSessionState is on st.session_state.unserializable = unserializable_data()
UnserializableSessionStateError
priority_high

警告

runner.enforceSerializableSessionState设置为true时,Session State隐式使用pickle模块,该模块已知不安全。确保所有从Session State保存和检索的数据都是可信的,因为有可能构造恶意的pickle数据,在反序列化期间执行任意代码。切勿以不安全模式加载可能来自不可信来源的数据,或可能被篡改的数据。仅加载您信任的数据

在使用会话状态时,需要注意以下一些限制:

  • 只要标签页打开并连接到Streamlit服务器,会话状态就会存在。一旦关闭标签页,存储在会话状态中的所有内容都会丢失。
  • 会话状态不会被持久化。如果 Streamlit 服务器崩溃,那么存储在会话状态中的所有内容都会被清除
  • 有关会话状态API的注意事项和限制,请参阅API限制
forum

还有问题吗?

我们的 论坛 充满了有用的信息和Streamlit专家。