Button behavior and examples
Summary
使用st.button创建的按钮不保留状态。它们在点击导致的脚本重新运行时返回True,并在下一次脚本重新运行时立即返回False。如果显示的元素嵌套在if st.button('Click me'):中,当按钮被点击时,该元素将可见,并在用户采取下一个操作时立即消失。这是因为脚本重新运行,按钮返回值变为False。
在本指南中,我们将说明按钮的使用并解释常见的误解。继续阅读以查看使用st.session_state扩展st.button的各种示例。最后还包括了反模式。继续并打开您最喜欢的代码编辑器,以便在阅读时可以使用streamlit run运行示例。如果您还没有运行自己的Streamlit脚本,请查看Streamlit的基本概念。
When to use if st.button()
当代码根据按钮的值进行条件判断时,它会在按钮被点击时执行一次,而不会再次执行(直到按钮再次被点击)。
适合嵌套在按钮内部:
- 立即消失的临时消息。
- 每次点击时保存数据到会话状态、文件或数据库的过程。
在按钮内部嵌套是不好的:
- 用户继续时应持续显示的项。
- 其他小部件在使用时会导致脚本重新运行。
- 既不修改会话状态也不写入文件/数据库的进程。*
* 当需要一次性结果时,这可能是合适的。如果你有一个“验证”按钮,那可能是一个直接基于按钮的条件过程。它可以用来创建一个提示,显示“有效”或“无效”,而无需保留该信息。
Common logic with buttons
Show a temporary message with a button
如果你想给用户一个快速按钮来检查条目是否有效,但不希望在用户继续操作时保持该检查显示。
在这个例子中,用户可以点击一个按钮来检查他们的animal字符串是否在animal_shelter列表中。当用户点击“检查可用性”时,他们会看到“我们有那种动物!”或“我们没有那种动物。”如果他们更改了st.text_input中的动物,脚本会重新运行,消息会消失,直到他们再次点击“检查可用性”。
import streamlit as st
animal_shelter = ['cat', 'dog', 'rabbit', 'bird']
animal = st.text_input('Type an animal')
if st.button('Check availability'):
have_it = animal.lower() in animal_shelter
'We have that animal!' if have_it else 'We don\'t have that animal.'
注意:上面的例子使用了magic在前端渲染消息。
Stateful button
如果你希望点击的按钮继续保持True状态,可以在st.session_state中创建一个值,并在回调函数中使用按钮将该值设置为True。
import streamlit as st
if 'clicked' not in st.session_state:
st.session_state.clicked = False
def click_button():
st.session_state.clicked = True
st.button('Click me', on_click=click_button)
if st.session_state.clicked:
# The message and nested widget will remain on the page
st.write('Button clicked!')
st.slider('Select a value')
Toggle button
如果你想要一个按钮像切换开关一样工作,可以考虑使用st.checkbox。否则,你可以使用一个带有回调函数的按钮来反转保存在st.session_state中的布尔值。
在这个例子中,我们使用st.button来切换另一个小部件的开关。通过在st.session_state中的某个值上条件性地显示st.slider,用户可以与滑块交互而不会使其消失。
import streamlit as st
if 'button' not in st.session_state:
st.session_state.button = False
def click_button():
st.session_state.button = not st.session_state.button
st.button('Click me', on_click=click_button)
if st.session_state.button:
# The message and nested widget will remain on the page
st.write('Button is on!')
st.slider('Select a value')
else:
st.write('Button is off!')
或者,您可以在滑块的disabled参数中使用st.session_state中的值。
import streamlit as st
if 'button' not in st.session_state:
st.session_state.button = False
def click_button():
st.session_state.button = not st.session_state.button
st.button('Click me', on_click=click_button)
st.slider('Select a value', disabled=st.session_state.button)
Buttons to continue or control stages of a process
另一种替代将内容嵌套在按钮中的方法是使用st.session_state中的值来指定过程的“步骤”或“阶段”。在这个例子中,我们的脚本有四个阶段:
- 在用户开始之前。
- 用户输入他们的名字。
- 用户选择一种颜色。
- 用户会收到一条感谢信息。
开始处的按钮将阶段从0推进到1。结束处的按钮将阶段从3重置为0。在第1和第2阶段使用的其他小部件具有设置阶段的回调。如果您有一个具有依赖步骤的过程,并希望保持先前阶段可见,这样的回调会迫使用户在更改较早的小部件时重新追溯后续阶段。
import streamlit as st
if 'stage' not in st.session_state:
st.session_state.stage = 0
def set_state(i):
st.session_state.stage = i
if st.session_state.stage == 0:
st.button('Begin', on_click=set_state, args=[1])
if st.session_state.stage >= 1:
name = st.text_input('Name', on_change=set_state, args=[2])
if st.session_state.stage >= 2:
st.write(f'Hello {name}!')
color = st.selectbox(
'Pick a Color',
[None, 'red', 'orange', 'green', 'blue', 'violet'],
on_change=set_state, args=[3]
)
if color is None:
set_state(2)
if st.session_state.stage >= 3:
st.write(f':{color}[Thank you!]')
st.button('Start Over', on_click=set_state, args=[0])
Buttons to modify st.session_state
如果你在按钮内部修改st.session_state,你必须考虑该按钮在脚本中的位置。
一个小问题
在这个例子中,我们在修改st.session_state.name的按钮前后都访问了它。当点击按钮("Jane"或"John")时,脚本会重新运行。按钮前显示的信息会滞后于按钮后写入的信息。按钮前的st.session_state中的数据不会更新。当脚本执行按钮函数时,更新st.session_state的条件代码才会创建更改。因此,这个更改会在按钮后反映出来。
import streamlit as st
import pandas as pd
if 'name' not in st.session_state:
st.session_state['name'] = 'John Doe'
st.header(st.session_state['name'])
if st.button('Jane'):
st.session_state['name'] = 'Jane Doe'
if st.button('John'):
st.session_state['name'] = 'John Doe'
st.header(st.session_state['name'])
回调中使用的逻辑
回调是一种修改st.session_state的简洁方式。回调在脚本重新运行之前执行,因此按钮相对于访问数据的位置并不重要。
import streamlit as st
import pandas as pd
if 'name' not in st.session_state:
st.session_state['name'] = 'John Doe'
def change_name(name):
st.session_state['name'] = name
st.header(st.session_state['name'])
st.button('Jane', on_click=change_name, args=['Jane Doe'])
st.button('John', on_click=change_name, args=['John Doe'])
st.header(st.session_state['name'])
嵌套在带有重新运行功能的按钮中的逻辑
虽然通常更倾向于使用回调来避免额外的重新运行,但我们的第一个'John Doe'/'Jane Doe'示例可以通过添加st.rerun来修改。如果你需要在修改数据的按钮之前访问st.session_state中的数据,你可以在更改提交后包含st.rerun来重新运行脚本。这意味着当按钮被点击时,脚本将重新运行两次。
import streamlit as st
import pandas as pd
if 'name' not in st.session_state:
st.session_state['name'] = 'John Doe'
st.header(st.session_state['name'])
if st.button('Jane'):
st.session_state['name'] = 'Jane Doe'
st.rerun()
if st.button('John'):
st.session_state['name'] = 'John Doe'
st.rerun()
st.header(st.session_state['name'])
Buttons to modify or reset other widgets
当按钮用于修改或重置另一个小部件时,与上述修改st.session_state的示例相同。然而,存在一个额外的考虑:如果具有该键的小部件已经在当前脚本运行的页面上呈现,则无法修改st.session_state中的键值对。
重要
不要这样做!
import streamlit as st
st.text_input('Name', key='name')
# These buttons will error because their nested code changes
# a widget's state after that widget within the script.
if st.button('Clear name'):
st.session_state.name = ''
if st.button('Streamlit!'):
st.session_state.name = ('Streamlit')
选项1:为按钮使用一个键,并将逻辑放在小部件之前
如果你为按钮分配了一个键,你可以通过在st.session_state中使用它的值来根据按钮的状态编写条件代码。这意味着依赖于按钮的逻辑可以在按钮之前出现在你的脚本中。在下面的示例中,我们在st.session_state上使用.get()方法,因为当脚本第一次运行时,按钮的键将不存在。.get()方法如果找不到键将返回False。否则,它将返回键的值。
import streamlit as st
# Use the get method since the keys won't be in session_state
# on the first script run
if st.session_state.get('clear'):
st.session_state['name'] = ''
if st.session_state.get('streamlit'):
st.session_state['name'] = 'Streamlit'
st.text_input('Name', key='name')
st.button('Clear name', key='clear')
st.button('Streamlit!', key='streamlit')
选项2:使用回调函数
import streamlit as st
st.text_input('Name', key='name')
def set_name(name):
st.session_state.name = name
st.button('Clear name', on_click=set_name, args=[''])
st.button('Streamlit!', on_click=set_name, args=['Streamlit'])
选项3:使用容器
通过使用st.container,您可以在脚本和前端视图(网页)中以不同的顺序显示小部件。
import streamlit as st
begin = st.container()
if st.button('Clear name'):
st.session_state.name = ''
if st.button('Streamlit!'):
st.session_state.name = ('Streamlit')
# The widget is second in logic, but first in display
begin.text_input('Name', key='name')
Buttons to add other widgets dynamically
当动态向页面添加小部件时,确保使用索引来保持键的唯一性,避免出现DuplicateWidgetID错误。在这个例子中,我们定义了一个函数display_input_row,它渲染一行小部件。该函数接受一个index作为参数。display_input_row渲染的小部件在其键中使用index,以便display_input_row可以在单个脚本重新运行时多次执行,而不会重复任何小部件键。
import streamlit as st
def display_input_row(index):
left, middle, right = st.columns(3)
left.text_input('First', key=f'first_{index}')
middle.text_input('Middle', key=f'middle_{index}')
right.text_input('Last', key=f'last_{index}')
if 'rows' not in st.session_state:
st.session_state['rows'] = 0
def increase_rows():
st.session_state['rows'] += 1
st.button('Add person', on_click=increase_rows)
for i in range(st.session_state['rows']):
display_input_row(i)
# Show the results
st.subheader('People')
for i in range(st.session_state['rows']):
st.write(
f'Person {i+1}:',
st.session_state[f'first_{i}'],
st.session_state[f'middle_{i}'],
st.session_state[f'last_{i}']
)
Buttons to handle expensive or file-writing processes
当你有耗时的进程时,设置它们在点击按钮时运行,并将结果保存到st.session_state中。这允许你在不必要重新执行的情况下继续访问进程的结果。这对于保存到磁盘或写入数据库的进程特别有用。在这个例子中,我们有一个expensive_process,它依赖于两个参数:option和add。从功能上讲,add会改变输出,但option不会——option的存在是为了提供一个参数。
import streamlit as st
import pandas as pd
import time
def expensive_process(option, add):
with st.spinner('Processing...'):
time.sleep(5)
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6], 'C':[7, 8, 9]}) + add
return (df, add)
cols = st.columns(2)
option = cols[0].selectbox('Select a number', options=['1', '2', '3'])
add = cols[1].number_input('Add a number', min_value=0, max_value=10)
if 'processed' not in st.session_state:
st.session_state.processed = {}
# Process and save results
if st.button('Process'):
result = expensive_process(option, add)
st.session_state.processed[option] = result
st.write(f'Option {option} processed with add {add}')
result[0]
敏锐的观察者可能会认为,“这感觉有点像缓存。”我们只保存与一个参数相关的结果,但这种模式可以轻松扩展以保存与两个参数相关的结果。从这个意义上说,是的,它与缓存有一些相似之处,但也有一些重要的区别。当你在st.session_state中保存结果时,这些结果仅对当前用户的当前会话可用。如果你使用st.cache_data,则结果对所有用户的所有会话都可用。此外,如果你想更新保存的结果,你必须清除该函数的所有保存结果才能这样做。
Anti-patterns
以下是一些按钮可能出错的简化示例。请注意这些常见错误。
Buttons nested inside buttons
import streamlit as st
if st.button('Button 1'):
st.write('Button 1 was clicked')
if st.button('Button 2'):
# This will never be executed.
st.write('Button 2 was clicked')
Other widgets nested inside buttons
import streamlit as st
if st.button('Sign up'):
name = st.text_input('Name')
if name:
# This will never be executed.
st.success(f'Welcome {name}')
Nesting a process inside a button without saving to session state
import streamlit as st
import pandas as pd
file = st.file_uploader("Upload a file", type="csv")
if st.button('Get data'):
df = pd.read_csv(file)
# This display will go away with the user's next action.
st.write(df)
if st.button('Save'):
# This will always error.
df.to_csv('data.csv')
还有问题吗?
我们的 论坛 充满了有用的信息和Streamlit专家。