Streamlit 讲解专栏(四):深入理解按钮行为
  buKgUvcX42BW 2023年11月02日 78 0

在这里插入图片描述

1 前言

欢迎来到我们关于Streamlit按钮行为的全面指南!按钮在Streamlit应用程序中是用户交互的基本组件。无论你是刚开始接触Streamlit还是已经有了一些经验,本指南都将为你提供如何在应用程序中有效使用按钮的坚实基础。在本文中,我们还将参考我之前写的三篇相关文章:

本篇指南将深入探讨按钮行为的细微差别,探索各种示例,并解释常见的误解。 使用 st.button 函数创建的按钮具有独特的特点,这些特点对于理解至关重要。具体而言,按钮在点击后不会保留状态。相反,当按钮被点击后,脚本会重新运行,并且按钮在下一次脚本重新运行时立即返回到原始状态。如果在st.button函数中嵌套了可见元素,则该元素将在按钮被点击时显示,并在用户进行下一次操作后立即消失。这是因为脚本重新运行,按钮的返回值在按钮点击事件中变为True,并在下一次脚本重新运行时返回为False。

在本章中,我们将展示多个示例,以说明在利用Streamlit的st.session_state时如何实际使用按钮。建议您已经安装了Streamlit并熟悉其主要概念。

2 Streamlit中if st.button()的使用时机

这个函数的使用可以根据按钮的值来决定执行某段代码,点击按钮后只执行一次,直到再次点击按钮。

适合将if st.button()嵌套在里面的场景包括:

✅ 瞬时消失的消息:一旦按钮被点击,显示消息但立即消失。

✅ 每次点击后执行的过程:将数据保存到会话状态、文件或数据库。

而不适合将if st.button()嵌套在里面的场景包括:

❌ 需要在用户继续操作时保留的显示项。

❌ 引起脚本重新运行的其他小部件使用。

❌ 既不修改会话状态也不写入文件/数据库的过程*。(*当需要一次性结果时,这种情况也可以接受。例如,如果你有一个“验证”按钮,可以将它作为直接依赖于按钮的流程,用于创建一个弹出消息,告诉用户'有效'或'无效',而不需要保留这些信息。)

3 按钮的逻辑

3.1 通过按钮显示临时消息的常用逻辑

如果你想给用户一个快速的按钮,让他们检查一个条目是否有效,但不想在用户继续操作时继续显示该检查结果。

在这个示例中,用户可以点击一个按钮来检查他们的字符串是否在列表中。当用户点击“检查可用性”按钮时,他们将看到“我们有这个动物!”或“我们没有这个动物。”如果他们在st.text_input中更改了动物的输入,脚本将重新运行,并且消息会在他们再次点击“检查可用性”按钮之前消失。

import streamlit as st

animal_shelter = ['cat', 'dog', 'rabbit', 'bird']

animal = st.text_input('输入一个动物')

if st.button('检查可用性'):
    have_it = animal.lower() in animal_shelter
    '我们有这个动物!' if have_it else '我们没有这个动物。'

在这里插入图片描述 在这里插入图片描述

3.2 状态保留按钮

在这个例子中,我们可以使用Streamlit的session_state功能来实现这个目标。我将为你展示如何创建一个变量,然后使用按钮点击事件将该变量的值设置为True。 首先,我们需要导入Streamlit库:

import streamlit as st

在使用session_state之前,我们需要确认该变量是否已经存在于session_state中,如果不存在,我们可以设置它的初始值为False:

if 'clicked' not in st.session_state:
    st.session_state.clicked = False

接下来,我们定义一个回调函数click_button(),在按钮点击事件发生时,我们将clicked变量的值设置为True:

def click_button():
    st.session_state.clicked = True

现在,我们可以创建一个按钮,并使用on_click参数将回调函数与按钮关联起来:

st.button('点击我', on_click=click_button)

最后,我们可以通过检查clicked变量的值来确定按钮是否被点击了。如果点击了按钮,我们可以在页面上保留相应的消息和嵌套小部件:

if st.session_state.clicked:
    # 消息和嵌套小部件将保留在页面上
    st.write('按钮已点击!')
    st.slider('选择一个值')

在这里插入图片描述

3.3 切换按钮

如果你想让按钮像一个切换开关一样工作,那么可以考虑使用st.checkbox。否则,你可以使用一个带有回调函数的按钮来反转保存在st.session_state中的布尔值。

在这个例子中,我们使用一个按钮来控制另一个小部件的显示与隐藏。通过根据button变量的值在条件语句中显示或隐藏st.slider,用户可以在不让它消失的情况下与滑动条进行交互。

首先,我们需要导入Streamlit库:

import streamlit as st

在使用session_state之前,我们需要确认该变量是否已经存在于session_state中,如果不存在,我们可以设置它的初始值为False:

if 'button' not in st.session_state:
    st.session_state.button = False

接下来,我们定义一个回调函数click_button(),在按钮点击事件发生时,我们将button变量的值取反:

def click_button():
    st.session_state.button = not st.session_state.button

现在,我们可以创建一个按钮,并使用on_click参数将回调函数与按钮关联起来:

st.button('点击我', on_click=click_button)

然后,我们可以通过检查button变量的值来确定按钮是否处于打开状态。如果按钮处于打开状态,我们将保留相应的消息和嵌套小部件:

if st.session_state.button:
    # 消息和嵌套小部件将保留在页面上
    st.write('按钮已打开!')
    st.slider('选择一个值')
else:
    st.write('按钮已关闭!')

在这里插入图片描述

3.4 控制流程的按钮

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

  1. 用户开始之前。
  2. 用户输入姓名。
  3. 用户选择颜色。
  4. 用户收到感谢消息。

在开始阶段,一个按钮可以将阶段从0增加到1。在结束阶段,一个按钮可以将阶段从3重置为0。在阶段1和2中使用的其他小部件具有回调函数来设置阶段。如果你的流程有依赖步骤,并且想保持之前的阶段可见,那么这样的回调函数会强制用户在更改之前的小部件时重新跟踪后续阶段。

首先,我们需要导入Streamlit库:

import streamlit as st

在使用session_state之前,我们需要确认该变量是否已经存在于session_state中,如果不存在,我们可以设置它的初始值为0:

if 'stage' not in st.session_state:
    st.session_state.stage = 0

接下来,我们定义一个函数set_state(i),用于设置阶段变量的值:

def set_state(i):
    st.session_state.stage = i

然后,根据当前阶段的值,我们可以显示不同的内容。在阶段0中,我们可以创建一个按钮来将阶段增加到1:

if st.session_state.stage == 0:
    st.button('开始', on_click=set_state, args=[1])

在阶段1和2中,我们可以使用回调函数来设置阶段的值。例如,在阶段1中,我们可以使用text_input小部件来获取用户的姓名,并在值更改时将阶段设置为2:

if st.session_state.stage >= 1:
    name = st.text_input('姓名', on_change=set_state, args=[2])

在阶段2中,我们可以显示根据用户输入的姓名显示的消息,并通过使用selectbox小部件来让用户选择颜色。如果颜色选择框的值为None,我们可以将阶段设置回2:

if st.session_state.stage >= 2:
    st.write(f'你好,{name}!')
    color = st.selectbox(
        '选择一种颜色',
        [None, '红色', '橙色', '绿色', '蓝色', '紫色'],
        on_change=set_state, args=[3]
    )
    if color is None:
        set_state(2)

在阶段3中,我们可以显示一个带有感谢消息的消息,并创建一个按钮来将阶段重置为0:

if st.session_state.stage >= 3:
    st.write(f'😊谢谢你,{color}!')
    st.button('重新开始', on_click=set_state, args=[0])

在这里插入图片描述

3.5 动态添加小部件的按钮

在开始之前,我们需要确保使用唯一的索引来保持键的唯一性并避免错误。在本示例中,我们定义了一个display_input_row函数,用于渲染一行小部件。该函数接受一个索引作为参数。小部件的键(key)使用了传入的索引,以便可以在单个脚本重新运行时多次执行而不会重复任何小部件的键。

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

接下来,我们检查st.session_state中是否存在名为'rows'的键。如果不存在,我们将其初始值设置为0。

if 'rows' not in st.session_state:
    st.session_state['rows'] = 0

然后,我们定义一个increase_rows函数,在点击按钮时增加st.session_state['rows']的值。

def increase_rows():
    st.session_state['rows'] += 1

我们创建一个"Add person"按钮,并将它与increase_rows函数进行关联。

st.button('Add person', on_click=increase_rows)

接下来,我们使用一个循环来根据st.session_state['rows']的值动态地调用display_input_row函数。

for i in range(st.session_state['rows']):
    display_input_row(i)

最后,我们展示添加结果的部分。在一个循环中,我们根据st.session_state中的键和索引来获取相应的小部件的值,并将其展示在页面上。

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

通过点击"Add person"按钮,我们可以动态地添加小部件,并在页面上显示。在循环中,每个小部件都有一个唯一的键,因此我们可以轻松地获取和展示它们的值。 在这里插入图片描述

3.6 使用按钮处理耗时或写入文件的过程

介绍如何使用按钮来处理耗时或写入文件的过程,并将结果保存起来,以便在不必要重新执行的情况下继续访问。这对于保存到磁盘或写入数据库的过程尤其有帮助。 在本示例中,我们有一个expensive_process函数,它依赖于两个参数:option和add。函数执行相应的过程,并返回一个结果。值得注意的是,更改option会改变输出结果,而更改add只是提供了一个参数。

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)来显示输入选项和添加数值的小部件。使用selectbox和number_input分别接收用户的输入。在代码中,我们使用了st.columns以便将小部件在页面上水平排列。

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)

接下来,我们检查st.session_state中是否存在名为'processed'的键。如果不存在,我们将其初始化为空字典。

if 'processed' not in st.session_state:
    st.session_state.processed = {}

当用户点击"Process"按钮时,我们执行expensive_process函数,并将结果保存在st.session_state.processed中。

if st.button('Process'):
    result = expensive_process(option, add)
    st.session_state.processed[option] = result

最后,我们检查option是否在st.session_state.processed中,如果是的话,我们展示处理结果并打印出数据框。

if option in st.session_state.processed:
    st.write(f'Option {option} processed with add {add}')
    st.write(st.session_state.processed[option][0])

通过点击"Process"按钮,我们可以处理耗时的过程并将结果保存下来。如果用户改变了option,我们会重新运行相应的过程并更新结果。

需要注意的是,保存在st.session_state中的结果仅对当前用户的当前会话可见。如果使用st.cache_data代替,结果将对所有用户和所有会话可见。此外,如果要更新已保存的结果,必须清除该函数的所有保存结果。 在这里插入图片描述

4 常见的按钮反模式

在使用按钮时,以下是一些常见的错误做法,需要格外注意。

4.1 按钮嵌套在按钮内部

在下面的示例中,我们可以看到按钮2被嵌套在按钮1的内部。这种嵌套将导致按钮2永远不会被执行。因为当按钮1被点击时,按钮2的代码块将永远不会执行。

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

4.2 将其他小部件嵌套在按钮内部

在下面的示例中,我们可以看到当用户点击"Sign up"按钮时,会显示一个用于输入姓名的文本框。然而,当用户输入姓名后,欢迎消息不会被执行。这是因为欢迎消息的代码块被嵌套在"Sign up"按钮的代码块内部,而不是与之并列。

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

4.3 在按钮内部嵌套处理过程,但未保存到会话状态中

在下面的示例中,我们可以看到"Get data"按钮被点击时,我们会读取一个CSV文件,并在屏幕上展示结果。然而,当用户点击"Save"按钮时,将会遇到一个错误。这是因为我们在"Save"按钮的代码块内部尝试将数据保存为CSV文件,但是没有将数据保存到会话状态(session state)中,所以在"Save"按钮点击后,数据丢失了。

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

5 结语

感谢您阅读本篇博客!我们希望这篇博客对您理解如何使用按钮处理耗时或写入文件的过程有所帮助。如果您有任何问题或需要进一步的帮助,请随时留言。如果您觉得这篇博客对您有用,请点赞并关注我们的博客,以获取更多有用的信息和教程。谢谢您的支持!

在这里插入图片描述

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
buKgUvcX42BW