Using forms
当您不想每次用户输入后重新运行脚本时,st.form
可以帮助您!表单使得将用户输入批量处理为一次重新运行变得容易。本使用表单的指南提供了示例,并解释了用户如何与表单交互。
Example
在以下示例中,用户可以设置多个参数来更新地图。当用户更改参数时,脚本不会重新运行,地图也不会更新。当用户点击标有“更新地图”的按钮提交表单时,脚本将重新运行,地图将更新。
如果用户在任何时候点击表单外的“生成新点”,脚本将重新运行。如果用户在表单中有任何未提交的更改,这些更改将不会随重新运行一起发送。对表单所做的所有更改只有在表单本身提交时才会发送到Python后端。
import streamlit as st
import pandas as pd
import numpy as np
def get_data():
df = pd.DataFrame({
"lat": np.random.randn(200) / 50 + 37.76,
"lon": np.random.randn(200) / 50 + -122.4,
"team": ['A','B']*100
})
return df
if st.button('Generate new points'):
st.session_state.df = get_data()
if 'df' not in st.session_state:
st.session_state.df = get_data()
df = st.session_state.df
with st.form("my_form"):
header = st.columns([1,2,2])
header[0].subheader('Color')
header[1].subheader('Opacity')
header[2].subheader('Size')
row1 = st.columns([1,2,2])
colorA = row1[0].color_picker('Team A', '#0000FF')
opacityA = row1[1].slider('A opacity', 20, 100, 50, label_visibility='hidden')
sizeA = row1[2].slider('A size', 50, 200, 100, step=10, label_visibility='hidden')
row2 = st.columns([1,2,2])
colorB = row2[0].color_picker('Team B', '#FF0000')
opacityB = row2[1].slider('B opacity', 20, 100, 50, label_visibility='hidden')
sizeB = row2[2].slider('B size', 50, 200, 100, step=10, label_visibility='hidden')
st.form_submit_button('Update map')
alphaA = int(opacityA*255/100)
alphaB = int(opacityB*255/100)
df['color'] = np.where(df.team=='A',colorA+f'{alphaA:02x}',colorB+f'{alphaB:02x}')
df['size'] = np.where(df.team=='A',sizeA, sizeB)
st.map(df, size='size', color='color')
User interaction
如果一个小部件不在表单中,每当用户更改其值时,该小部件将触发脚本重新运行。对于具有键输入的小部件(st.number_input
,st.text_input
,st.text_area
),当用户点击或离开小部件时,新值会触发重新运行。用户还可以在光标处于小部件活动状态时按下Enter
键提交更改。
另一方面,如果一个小部件位于表单内部,当用户点击或离开该小部件时,脚本不会重新运行。对于表单内部的小部件,脚本将在表单提交时重新运行,并且表单内的所有小部件将将其更新后的值发送到Python后端。

用户可以在光标位于接受键盘输入的小部件中时,使用键盘上的Enter键提交表单。在st.number_input
和st.text_input
中,用户按下Enter键提交表单。在st.text_area
中,用户按下Ctrl+Enter/⌘+Enter提交表单。

Widget values
在表单提交之前,表单内的所有小部件都将具有默认值,就像表单外的小部件具有默认值一样。
import streamlit as st
with st.form("my_form"):
st.write("Inside the form")
my_number = st.slider('Pick a number', 1, 10)
my_color = st.selectbox('Pick a color', ['red','orange','green','blue','violet'])
st.form_submit_button('Submit my picks')
# This is outside the form
st.write(my_number)
st.write(my_color)
Forms are containers
当调用st.form
时,前端会创建一个容器。你可以像处理其他容器元素一样向该容器写入内容。也就是说,你可以使用Python的with
语句,如上例所示,或者你可以将表单容器分配给一个变量并直接调用其方法。此外,你可以在表单容器中的任何位置放置st.form_submit_button
。
import streamlit as st
animal = st.form('my_animal')
# This is writing directly to the main body. Since the form container is
# defined above, this will appear below everything written in the form.
sound = st.selectbox('Sounds like', ['meow','woof','squeak','tweet'])
# These methods called on the form container, so they appear inside the form.
submit = animal.form_submit_button(f'Say it with {sound}!')
sentence = animal.text_input('Your sentence:', 'Where\'s the tuna?')
say_it = sentence.rstrip('.,!?') + f', {sound}!'
if submit:
animal.subheader(say_it)
else:
animal.subheader(' ')
Processing form submissions
表单的目的是覆盖Streamlit的默认行为,即一旦用户做出更改,脚本就会重新运行。对于表单外的小部件,逻辑流程是:
- 用户在前端更改了小部件的值。
- 小部件的值在
st.session_state
和Python后端(服务器)中更新。 - 脚本重新运行开始。
- 如果小部件有回调函数,它将在页面重新运行之前作为前缀执行。
- 当重新运行期间执行更新后的小部件功能时,它会输出新值。
对于表单内的小部件,用户所做的任何更改(步骤1)在表单提交之前不会传递到Python后端(步骤2)。此外,表单内唯一可以具有回调函数的小部件是st.form_submit_button
。如果您需要使用新提交的值执行过程,您有三种主要模式可以这样做。
Execute the process after the form
如果您需要在表单提交后执行一次性过程,您可以将该过程条件化在st.form_submit_button
上,并在表单之后执行它。如果您需要过程的结果显示在表单上方,您可以使用容器来控制表单相对于输出的显示位置。
import streamlit as st
col1,col2 = st.columns([1,2])
col1.title('Sum:')
with st.form('addition'):
a = st.number_input('a')
b = st.number_input('b')
submit = st.form_submit_button('add')
if submit:
col2.title(f'{a+b:.2f}')
Use a callback with session state
您可以使用回调来执行一个过程,作为脚本重新运行的前缀。
重要
在回调中处理新更新的值时,不要通过args
或kwargs
参数直接将这些值传递给回调。您需要为在回调中使用的任何小部件分配一个键。如果您在回调的主体中从st.session_state
查找该小部件的值,您将能够访问新提交的值。请参见下面的示例。
import streamlit as st
if 'sum' not in st.session_state:
st.session_state.sum = ''
def sum():
result = st.session_state.a + st.session_state.b
st.session_state.sum = result
col1,col2 = st.columns(2)
col1.title('Sum:')
if isinstance(st.session_state.sum, float):
col2.title(f'{st.session_state.sum:.2f}')
with st.form('addition'):
st.number_input('a', key = 'a')
st.number_input('b', key = 'b')
st.form_submit_button('add', on_click=sum)
st.rerun
Use
如果你的进程影响了表单上方的内容,另一种选择是使用额外的重新运行。不过,这可能不太节省资源,并且可能不如上述选项理想。
import streamlit as st
if 'sum' not in st.session_state:
st.session_state.sum = ''
col1,col2 = st.columns(2)
col1.title('Sum:')
if isinstance(st.session_state.sum, float):
col2.title(f'{st.session_state.sum:.2f}')
with st.form('addition'):
a = st.number_input('a')
b = st.number_input('b')
submit = st.form_submit_button('add')
# The value of st.session_state.sum is updated at the end of the script rerun,
# so the displayed value at the top in col2 does not show the new sum. Trigger
# a second rerun when the form is submitted to update the value above.
st.session_state.sum = a + b
if submit:
st.rerun()
Limitations
- 每个表单必须包含一个
st.form_submit_button
。 st.button
和st.download_button
不能添加到表单中。st.form
不能嵌入到另一个st.form
中。- 回调函数只能分配给表单内的
st.form_submit_button
;表单中的其他小部件不能有回调。 - 表单内相互依赖的小部件不太可能特别有用。如果你将
widget1
的值传递给widget2
,而它们都在表单内,那么widget2
只会在表单提交时更新。
还有问题吗?
我们的 论坛 充满了有用的信息和Streamlit专家。