Button behavior and examples

使用st.button创建的按钮不保留状态。它们在点击导致的脚本重新运行时返回True,并在下一次脚本重新运行时立即返回False。如果显示的元素嵌套在if st.button('Click me'):中,当按钮被点击时,该元素将可见,并在用户采取下一个操作时立即消失。这是因为脚本重新运行,按钮返回值变为False

在本指南中,我们将说明按钮的使用并解释常见的误解。继续阅读以查看使用st.session_state扩展st.button的各种示例。最后还包括了反模式。继续并打开您最喜欢的代码编辑器,以便在阅读时可以使用streamlit run运行示例。如果您还没有运行自己的Streamlit脚本,请查看Streamlit的基本概念

当代码根据按钮的值进行条件判断时,它会在按钮被点击时执行一次,而不会再次执行(直到按钮再次被点击)。

适合嵌套在按钮内部:

  • 立即消失的临时消息。
  • 每次点击时保存数据到会话状态、文件或数据库的过程。

在按钮内部嵌套是不好的:

  • 用户继续时应持续显示的项。
  • 其他小部件在使用时会导致脚本重新运行。
  • 既不修改会话状态也不写入文件/数据库的进程。*

* 当需要一次性结果时,这可能是合适的。如果你有一个“验证”按钮,那可能是一个直接基于按钮的条件过程。它可以用来创建一个提示,显示“有效”或“无效”,而无需保留该信息。

如果你想给用户一个快速按钮来检查条目是否有效,但不希望在用户继续操作时保持该检查显示。

在这个例子中,用户可以点击一个按钮来检查他们的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在前端渲染消息。

如果你希望点击的按钮继续保持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')

如果你想要一个按钮像切换开关一样工作,可以考虑使用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)

另一种替代将内容嵌套在按钮中的方法是使用st.session_state中的值来指定过程的“步骤”或“阶段”。在这个例子中,我们的脚本有四个阶段:

  1. 在用户开始之前。
  2. 用户输入他们的名字。
  3. 用户选择一种颜色。
  4. 用户会收到一条感谢信息。

开始处的按钮将阶段从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])

如果你在按钮内部修改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'])

当按钮用于修改或重置另一个小部件时,与上述修改st.session_state的示例相同。然而,存在一个额外的考虑:如果具有该键的小部件已经在当前脚本运行的页面上呈现,则无法修改st.session_state中的键值对。

priority_high

重要

不要这样做!

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')

当动态向页面添加小部件时,确保使用索引来保持键的唯一性,避免出现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}'] )

当你有耗时的进程时,设置它们在点击按钮时运行,并将结果保存到st.session_state中。这允许你在不必要重新执行的情况下继续访问进程的结果。这对于保存到磁盘或写入数据库的进程特别有用。在这个例子中,我们有一个expensive_process,它依赖于两个参数:optionadd。从功能上讲,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,则结果对所有用户的所有会话都可用。此外,如果你想更新保存的结果,你必须清除该函数的所有保存结果才能这样做。

以下是一些按钮可能出错的简化示例。请注意这些常见错误。

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')
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}')
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')
forum

还有问题吗?

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