Streamlit(二)- Streamlit 架构与运行机制

文章目录

  • [一、Streamlit 架构与运行机制](#一、Streamlit 架构与运行机制)
    • [1. 运行应用](#1. 运行应用)
      • [1.1 基础运行命令](#1.1 基础运行命令)
      • [1.2 向脚本传递自定义参数](#1.2 向脚本传递自定义参数)
    • [2. 核心架构](#2. 核心架构)
      • [2.1 架构概览](#2.1 架构概览)
      • [2.2 服务端(Python Backend)](#2.2 服务端(Python Backend))
      • [2.3 客户端(Browser Frontend)](#2.3 客户端(Browser Frontend))
      • [2.4 架构对应用设计的影响](#2.4 架构对应用设计的影响)
      • [2.5 WebSockets 与会话管理](#2.5 WebSockets 与会话管理)
      • [2.6 会话亲和性](#2.6 会话亲和性)
    • [3. 应用界面](#3. 应用界面)
      • [3.1 应用菜单概览](#3.1 应用菜单概览)
      • [3.2 通用功能详解](#3.2 通用功能详解)
      • [3.3 开发者选项(Developer options)](#3.3 开发者选项(Developer options))
      • [3.4 菜单自定义(Customize the menu)](#3.4 菜单自定义(Customize the menu))
    • [4. 缓存机制](#4. 缓存机制)
      • [4.1 缓存核心装饰器](#4.1 缓存核心装饰器)
      • [4.2 数据结果缓存](#4.2 数据结果缓存)
      • [4.3 全局资源缓存](#4.3 全局资源缓存)
      • [4.4 缓存机制的工作原理](#4.4 缓存机制的工作原理)
      • [4.5 关键使用注意事项](#4.5 关键使用注意事项)
      • [4.6 自定义缓存行为](#4.6 自定义缓存行为)
    • [5. 会话状态](#5. 会话状态)
      • [5.1 什么是会话状态](#5.1 什么是会话状态)
      • [5.2 为什么需要会话状态](#5.2 为什么需要会话状态)
      • [5.3 会话状态的基本用法](#5.3 会话状态的基本用法)
      • [5.4 回调函数进阶用法](#5.4 回调函数进阶用法)
      • [5.5 会话状态与组件状态绑定](#5.5 会话状态与组件状态绑定)
      • [5.6 关键注意事项与限制](#5.6 关键注意事项与限制)
    • [6. 表单组件](#6. 表单组件)
      • [6.1 表单的核心作用](#6.1 表单的核心作用)
      • [6.2 表单的基础用法](#6.2 表单的基础用法)
      • [6.3 表单的交互行为](#6.3 表单的交互行为)
      • [6.4 表单容器的使用方式](#6.4 表单容器的使用方式)
      • [6.5 表单提交的处理方式](#6.5 表单提交的处理方式)
        • [6.5.1 提交后直接处理(推荐)](#6.5.1 提交后直接处理(推荐))
        • [6.5.2 配合回调函数处理](#6.5.2 配合回调函数处理)
      • [6.6 表单的关键限制与注意事项](#6.6 表单的关键限制与注意事项)
    • [7. 片段组件](#7. 片段组件)
      • [7.1 片段的核心作用](#7.1 片段的核心作用)
      • [7.2 片段的定义与使用](#7.2 片段的定义与使用)
        • [7.2.1 基础用法](#7.2.1 基础用法)
        • [7.2.2 在侧边栏或其他容器中使用](#7.2.2 在侧边栏或其他容器中使用)
      • [7.3 片段的执行流程](#7.3 片段的执行流程)
      • [7.4 片段的关键特性](#7.4 片段的关键特性)
      • [7.5 片段与其他功能的对比](#7.5 片段与其他功能的对比)
      • [7.6 注意事项与限制](#7.6 注意事项与限制)
    • [8. 组件行为(Widget Behavior)](#8. 组件行为(Widget Behavior))
      • [8.1 组件的核心组成](#8.1 组件的核心组成)
      • [8.2 组件身份与唯一标识](#8.2 组件身份与唯一标识)
        • [8.2.1 基于 Key 的标识(推荐)](#8.2.1 基于 Key 的标识(推荐))
        • [8.2.2 基于参数的标识](#8.2.2 基于参数的标识)
        • [8.2.3 避免重复组件错误](#8.2.3 避免重复组件错误)
      • [8.3 组件交互的执行顺序](#8.3 组件交互的执行顺序)
      • [8.4 组件的状态保持与重置](#8.4 组件的状态保持与重置)
      • [8.5 组件生命周期与状态持久化](#8.5 组件生命周期与状态持久化)
      • [8.6 组件与URL参数绑定(Query Params)](#8.6 组件与URL参数绑定(Query Params))
      • [8.7 关键使用注意事项](#8.7 关键使用注意事项)

一、Streamlit 架构与运行机制

1. 运行应用

Streamlit 的运行方式非常简单,只需在普通的 Python 脚本中引入 Streamlit 命令,然后通过以下方式启动即可。

1.1 基础运行命令

最常用、最便捷的启动方式是使用 streamlit run 命令,执行该命令后,Streamlit 会自动启动一个本地服务器,并在默认浏览器中打开你的应用页面,无需额外配置 Web 服务。

bash 复制代码
streamlit run your_script.py

1.2 向脚本传递自定义参数

如果需要向 Python 脚本传递自定义参数,必须在参数前添加 --,这种方式可以灵活地为应用传递配置参数,方便实现不同场景下的应用配置。

bash 复制代码
streamlit run your_script.py -- [--script args]

2. 核心架构

Streamlit 应用采用典型的 客户端-服务器(Client-Server) 架构模式,理解这一架构是开发、部署和排错的关键。

2.1 架构概览

  • 服务端(Server):即你的 Python 后端程序,由 Streamlit 服务器运行,负责执行代码、处理所有计算、存储数据,并响应客户端请求。
  • 客户端(Client):即用户的浏览器,负责展示应用界面、响应用户交互,并将操作事件发送给服务端。
  • 本地开发时,服务端和客户端运行在同一台机器上;当应用被网络用户访问时,二者则运行在不同的设备上。

2.2 服务端(Python Backend)

当执行 streamlit run your_app.py 命令时,本地会启动一个 Streamlit 服务器。这个服务器是应用的"大脑":

  • 它运行在初始化应用的机器上,所有用户请求的计算任务都由它执行。
  • 它为每一个连接的用户维护独立的会话状态,确保不同用户的操作互不干扰。

2.3 客户端(Browser Frontend)

用户通过浏览器访问应用时,其设备就成为了 Streamlit 客户端:

  • 它负责渲染由服务端发送的 UI 组件,如文本、图表、按钮等。
  • 当用户与界面交互(如点击按钮、输入文本)时,客户端会将这些事件发送给服务端处理。

2.4 架构对应用设计的影响

在开发 Streamlit 应用时,客户端-服务器架构带来了几个重要的设计考量:

  1. 服务端性能瓶颈:服务端需要处理所有用户的计算和存储请求,其配置必须能应对并发访问的压力。
  2. 用户文件访问限制 :服务端无法直接访问客户端的本地文件系统,只能处理用户通过 st.file_uploader 等组件上传的文件。
  3. 外设交互限制:若应用需要访问摄像头等外设,必须通过 Streamlit 组件让设备在客户端浏览器中操作,并通过网络将数据传回服务端。
  4. 进程运行位置:应用中打开浏览器或运行外部程序的代码,都会在服务端执行,而非用户的客户端设备。
  5. 部署负载均衡限制:在多服务器部署中,若不启用会话亲和性,会导致会话状态丢失、媒体文件加载失败等问题。

2.5 WebSockets 与会话管理

Streamlit 的服务端基于 Tornado 框架构建,核心依赖 WebSockets 协议:

  • 它在客户端和服务端之间建立一条持久化的双向通信通道,实现服务端向客户端的实时推送更新。
  • 每个浏览器标签页或窗口都会创建独立的 WebSocket 连接,对应服务端的一个独立会话。

2.6 会话亲和性

在大规模部署中,为解决负载均衡导致的会话问题,通常采用以下方案:

  • 启用会话亲和性(粘滞会话):确保同一用户的所有请求都被转发到同一个服务器实例上,这是最通用的解决方案。
  • 转换媒体文件格式:将图片、音频等媒体文件转换为 Base64 编码的 Data URI,通过 WebSocket 而非 HTTP 传递。
  • 使用外部存储:将动态生成的文件保存到 S3 等稳定的外部存储服务中,再将外部 URL 传递给 Streamlit。

3. 应用界面

Streamlit 应用的右上角自带了一套内置交互控件,我们将其统称为 应用界面(The app chrome),包含状态区、工具栏和应用菜单,既能辅助开发者调试,也能为普通用户提供便捷功能。

3.1 应用菜单概览

点击右上角的菜单图标,即可打开应用菜单,菜单分为两部分:

  • 通用选项(所有用户可见):包括重新运行、设置、打印、录屏、关于等功能。
  • 开发者选项(默认仅本地开发/管理员可见):包括清除缓存、一键部署等功能。

3.2 通用功能详解

  1. Rerun(重新运行)

    手动触发应用重新运行,不会重置会话状态,st.session_state 和组件值会被保留。快捷键:R(非输入框聚焦时)。

  2. Settings(设置)

    控制应用运行时的外观与行为:

    • 可设置浅色/深色/跟随系统主题,还能自定义主题配色
    • 可强制开启宽屏模式,即使脚本未通过 st.set_page_config 设置
    • 本地开发时,可配置代码修改后的自动刷新行为
  3. Print(打印)

    调用浏览器的打印功能,支持将应用导出为PDF:

    • 可先展开/收起侧边栏,决定是否在打印结果中包含侧边栏
    • 深色模式打印时,建议在打印设置中开启「背景图形」
    • 可通过调整打印缩放比例,避免元素被截断
  4. Record a screencast(录屏)

    直接在浏览器内录制应用操作视频,支持同时录制麦克风音频:

    • 仅支持 Chrome、Edge、Firefox 等主流浏览器的最新版本
    • 录制时标签页会显示红色圆点提示,结束后视频将保存到浏览器下载目录
  5. About(关于)

    查看当前运行的 Streamlit 版本,开发者可通过 st.set_page_config 自定义此处显示的信息。

3.3 开发者选项(Developer options)

默认仅在本地开发或 Streamlit Community Cloud 管理员账号下可见,包含以下核心功能:

  • Clear cache(清除缓存) :重置 st.cache_datast.cache_resource 的所有缓存,快捷键:C(非输入框聚焦时)
  • Deploy this app(一键部署):将本地项目直接部署到 Streamlit Community Cloud,前提是代码已推送到在线 GitHub 仓库

可通过配置修改菜单的显示行为,核心配置项为 client.toolbarMode,支持以下模式:

模式 效果
auto(默认) 本地开发/管理员账号下显示开发者选项,其他情况隐藏
developer 向所有用户显示完整开发者选项
viewer 隐藏所有开发者选项,仅显示通用功能
minimal 仅显示外部配置的选项(如通过 st.set_page_config 设置)

4. 缓存机制

Streamlit 每次交互都会重新运行整个脚本,当涉及数据加载、模型推理等耗时操作时,会导致应用卡顿。缓存机制正是为了解决这一问题,通过存储函数执行结果,避免重复计算,大幅提升应用性能。

4.1 缓存核心装饰器

Streamlit 提供了两个核心缓存装饰器,分别适用于不同场景:

装饰器 适用场景 核心特点
@st.cache_data 数据处理、计算类函数 缓存返回值(如 DataFrame、列表、数值),会话间共享,可配置过期时间
@st.cache_resource 资源初始化类函数 缓存全局资源(如模型、数据库连接),单例模式,跨会话复用

4.2 数据结果缓存

@st.cache_data用于缓存函数的返回数据,是数据加载、处理场景的首选。
示例:

python 复制代码
import streamlit as st
import pandas as pd

# 缓存数据加载函数,避免重复读取文件
@st.cache_data
def load_data(file_path):
    df = pd.read_csv(file_path)
    return df

# 调用函数,仅首次执行会读取文件
data = load_data("large_data.csv")
st.dataframe(data)

核心特性:

  • 参数敏感:函数的参数值变化时,会触发重新计算并更新缓存

  • 会话间共享:同一参数的计算结果会被所有用户共享,节省服务器资源

  • 可配置过期 :通过 ttl 参数设置缓存过期时间(秒),自动刷新数据

    python 复制代码
    # 缓存1小时后自动刷新
    @st.cache_data(ttl=3600)
    def load_database_data():
        # 从数据库查询数据
        return query_result

4.3 全局资源缓存

@st.cache_resource用于缓存模型、数据库连接等重量级资源,这些资源创建成本高、不适合重复初始化。

示例:

python 复制代码
import streamlit as st
import torch

# 缓存模型加载,仅在应用启动时加载一次
@st.cache_resource
def load_model():
    model = torch.load("my_model.pth")
    model.eval()
    return model

# 所有用户会话共享同一个模型实例
model = load_model()

核心特性:

  • 单例模式:整个应用中仅创建一个实例,跨所有会话复用

  • 不复制对象:直接缓存对象本身,不进行序列化/反序列化,适合大型模型

  • 适合连接复用 :数据库连接、API客户端等资源可通过该装饰器实现全局复用

    python 复制代码
    @st.cache_resource
    def init_db_connection():
        conn = psycopg2.connect("postgresql://user:pass@host/db")
        return conn

4.4 缓存机制的工作原理

  1. 缓存键生成:Streamlit 会根据函数名、代码哈希、参数值生成唯一的缓存键
  2. 首次执行:函数首次调用时,执行逻辑并将结果/资源存入缓存
  3. 后续调用 :再次调用时,检查缓存键是否存在:
    • 存在则直接返回缓存结果,跳过执行
    • 不存在则重新执行并更新缓存
  4. 会话隔离与共享cache_data 结果会话间共享,cache_resource 实例全局单例

4.5 关键使用注意事项

  • 缓存函数需纯函数:输入参数相同则输出必须相同,避免包含随机数、时间戳等可变逻辑
  • 参数必须可哈希 :列表、字典等不可哈希对象会导致缓存失效,可通过 hash_funcs 自定义哈希方式
  • 避免副作用:缓存函数内不应包含修改全局变量、发送邮件等外部操作
  • 手动清除缓存 :可通过应用菜单的「Clear cache」或快捷键 C 清除所有缓存

4.6 自定义缓存行为

  1. 自定义哈希函数 :处理不可哈希参数

    python 复制代码
    @st.cache_data(hash_funcs={MyCustomClass: lambda obj: obj.unique_id})
    def process_custom_object(obj):
        # 处理自定义对象
        return result
  2. 自定义加载提示 :修改缓存加载时的默认提示文本

    python 复制代码
    @st.cache_data(show_spinner="正在加载数据,请稍候...")
    def load_data():
        # 耗时操作

5. 会话状态

Streamlit 的脚本在每次用户交互时都会从头重新运行 ,普通变量会被反复重置,无法保存用户的操作状态。会话状态(Session State) 就是为了解决这个问题,它能在整个用户会话期间持久保存变量,让你的应用具备"记忆"能力。

5.1 什么是会话状态

  • 会话(Session):用户在浏览器中打开的一个 Streamlit 标签页,就是一个独立的会话。每个会话都是相互隔离的。
  • 会话状态(st.session_state:一个类似 Python 字典的对象,用于在脚本多次重新运行之间,为每个用户会话独立保存和共享变量。
  • 它不仅能保存数据,还能与组件(如按钮、滑块)绑定,实现状态联动,甚至支持多页面应用间的数据传递。

5.2 为什么需要会话状态

看一个简单的计数器例子,不使用会话状态时,代码无法正常工作:

python 复制代码
import streamlit as st

st.title("计数器(错误示例)")
count = 0  # 每次交互都会重置为0
if st.button("点击+1"):
    count += 1
st.write(f"当前计数: {count}")

问题分析 :每次点击按钮,脚本都会重新运行,count 会被重置为 0,所以无论点击多少次,计数永远显示为 1

而使用 st.session_state 后,就能完美解决这个问题;count 的值会被保存在会话状态中,每次点击都会正确累加。

python 复制代码
import streamlit as st

st.title("计数器(正确示例)")
# 初始化状态变量
if 'count' not in st.session_state:
    st.session_state.count = 0

if st.button("点击+1"):
    st.session_state.count += 1

st.write(f"当前计数: {st.session_state.count}")

5.3 会话状态的基本用法

st.session_state 的 API 非常直观,支持两种访问方式:字典式和属性式。

  1. 初始化状态变量

    python 复制代码
    # 方式1:字典式访问(推荐)
    if 'key_name' not in st.session_state:
        st.session_state['key_name'] = "初始值"
    
    # 方式2:属性式访问
    if 'key_name' not in st.session_state:
        st.session_state.key_name = "初始值"
  2. 读取和更新状态变量

    python 复制代码
    # 读取值
    st.write(st.session_state['key_name'])
    st.write(st.session_state.key_name)
    
    # 更新值
    st.session_state['key_name'] = "新值"
    st.session_state.key_name = "新值"

    注意:访问未初始化的状态变量会抛出异常,因此务必先判断变量是否存在再使用。

5.4 回调函数进阶用法

回调函数是在组件状态改变时自动执行的函数,常与会话状态配合使用,实现更复杂的交互逻辑。

  1. 基础回调示例

    python 复制代码
    import streamlit as st
    
    st.title("计数器(回调示例)")
    if 'count' not in st.session_state:
        st.session_state.count = 0
    
    # 定义回调函数
    def increment():
        st.session_state.count += 1
    
    # 按钮点击时触发回调
    st.button("点击+1", on_click=increment)
    st.write(f"当前计数: {st.session_state.count}")
  2. 传递参数的回调

    通过 argskwargs 可以向回调函数传递参数:

    python 复制代码
    import streamlit as st
    
    st.title("带参数的计数器")
    if 'count' not in st.session_state:
        st.session_state.count = 0
    
    # 让用户输入步长
    step = st.number_input("步长", value=1, step=1)
    
    def increment(step_value):
        st.session_state.count += step_value
    
    # 传递参数给回调函数
    st.button("增加", on_click=increment, args=(step,))
    st.write(f"当前计数: {st.session_state.count}")

5.5 会话状态与组件状态绑定

Streamlit 组件(如滑块、输入框)的值会自动存储在会话状态中 ,通过 key 参数可以直接关联:

python 复制代码
import streamlit as st

# 创建滑块,并指定key
st.slider("温度(°C)", min_value=-100.0, max_value=100.0, key="celsius")

# 直接通过key获取组件的值
st.write(f"当前温度: {st.session_state.celsius}°C")

重要限制 :无法通过 st.session_state 直接修改 st.buttonst.file_uploader 等组件的状态,否则会抛出异常。

5.6 关键注意事项与限制

  1. 会话生命周期:状态仅存在于当前会话(浏览器标签页)中,关闭标签页或服务器重启后,状态会被清除。
  2. 可序列化要求:默认情况下,会话状态中的数据会被序列化(Pickle),因此应避免存储不可序列化的对象(如 lambda 函数)。
  3. 安全风险:Pickle 序列化存在安全隐患,不要加载来自不可信来源的数据。
  4. 线程安全:每个用户会话是独立的,不同用户的状态不会互相干扰。

6. 表单组件

在 Streamlit 中,普通组件每次值变化都会触发脚本重新运行,而 表单(st.form 可以将多个输入组件打包,仅在用户点击提交按钮时一次性处理所有输入,避免频繁重运行,提升交互体验和性能。

6.1 表单的核心作用

  • 批量提交输入:用户修改表单内的滑块、输入框等组件时,脚本不会立即重运行,仅在提交时统一处理。
  • 减少不必要计算:适用于参数较多、计算耗时的场景(如地图配置、数据筛选),避免每改一个参数就触发一次全量重运行。
  • 结构化交互:将相关输入组件分组,提升界面的逻辑清晰度。

6.2 表单的基础用法

使用 with st.form("表单名"): 定义表单容器,内部添加输入组件,并通过 st.form_submit_button("提交按钮文字") 创建提交按钮。

示例:地图参数配置表单

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("map_config_form"):
    st.subheader("地图样式配置")
    # 分栏布局
    color_col, opacity_col, size_col = st.columns(3)
    
    with color_col:
        colorA = st.color_picker('Team A 颜色', '#0000FF')
        colorB = st.color_picker('Team B 颜色', '#FF0000')
    with opacity_col:
        opacityA = st.slider('Team A 透明度', 20, 100, 50)
        opacityB = st.slider('Team B 透明度', 20, 100, 50)
    with size_col:
        sizeA = st.slider('Team A 点大小', 50, 200, 100, step=10)
        sizeB = st.slider('Team B 点大小', 50, 200, 100, step=10)
    
    # 提交按钮(表单内必须有一个)
    submitted = st.form_submit_button("更新地图")

# 提交后更新地图样式
if submitted:
    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')

6.3 表单的交互行为

  1. 表单内组件:修改表单内的输入框、滑块等组件时,脚本不会重运行,值变化仅保存在前端,直到表单提交才同步到后端。
  2. 表单外组件:修改表单外的组件(如普通按钮、滑块)会立即触发脚本重运行,表单内未提交的修改不会被保留。
  3. 提交方式
    • 点击表单内的 st.form_submit_button 按钮提交;
    • 文本输入框内按 Enter 键提交;
    • 文本域内按 Ctrl+Enter(Mac 为 Cmd+Enter)提交。

6.4 表单容器的使用方式

除了 with 语句,还可以直接为表单对象调用方法:

python 复制代码
import streamlit as st

# 创建表单对象
animal_form = st.form("animal_form")

# 向表单内添加内容
sound = animal_form.selectbox("动物叫声", ['meow', 'woof', 'squeak', 'tweet'])
submit_btn = animal_form.form_submit_button(f"Say it with {sound}!")
user_input = animal_form.text_input("你的句子:", "Where's the tuna?")

# 提交后显示结果
if submit_btn:
    animal_form.subheader(f"{user_input.rstrip('.,!?')} {sound}!")
else:
    animal_form.subheader("")

6.5 表单提交的处理方式

6.5.1 提交后直接处理(推荐)

在表单外通过判断提交按钮的状态,执行后续逻辑:

python 复制代码
import streamlit as st

col1, col2 = st.columns(2)
col1.title("加法计算器")

with st.form("addition_form"):
    a = st.number_input("输入数字 a")
    b = st.number_input("输入数字 b")
    submitted = st.form_submit_button("计算")

# 提交后执行计算
if submitted:
    col2.title(f"结果: {a + b:.2f}")
6.5.2 配合回调函数处理

通过 on_click 参数为提交按钮绑定回调函数,在提交时执行:

python 复制代码
import streamlit as st

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

def calculate_sum():
    st.session_state.sum = st.session_state.a + st.session_state.b

col1, col2 = st.columns(2)
col1.title("加法计算器")

with st.form("addition_form"):
    st.number_input("输入数字 a", key="a")
    st.number_input("输入数字 b", key="b")
    st.form_submit_button("计算", on_click=calculate_sum)

col2.title(f"结果: {st.session_state.sum:.2f}")

6.6 表单的关键限制与注意事项

  1. 必须包含提交按钮 :每个表单必须有且仅有一个 st.form_submit_button,否则无法提交。
  2. 组件限制
    • st.button 不能嵌入表单中;
    • 在表单中,回调函数仅能绑定到 st.form_submit_button,表单内其他组件不支持回调;
    • 表单内组件之间无法实时联动(如滑块修改输入框值),仅在提交后才会同步。
  3. 数据同步 :表单提交前,内部组件的值不会更新到 st.session_state,提交后才会一次性同步。

7. 片段组件

Streamlit 1.37.0 引入的 片段(Fragments) 功能,可以仅重运行应用的一部分代码,而不是整个脚本。这大幅提升了大型复杂应用的性能,让你能更精细地控制应用的执行流程。

7.1 片段的核心作用

片段(Fragments)的核心价值在于局部重运行,仅当用户与片段内的组件交互时,才会重新执行片段内的代码,而不会影响应用的其他部分。它主要适用于以下场景:

  • 应用包含多个耗时加载的可视化组件,希望某个筛选器仅更新对应的图表,而不重绘其他组件
  • 需要动态更新单个组件或一组组件(如实时数据仪表盘),避免全页重运行的开销
  • 表单提交后,无需重运行整个脚本,仅更新需要的结果区域

7.2 片段的定义与使用

通过 @st.fragment 装饰器,可以将任意函数标记为片段函数。当用户与片段内的组件交互时,只会重新执行该片段函数

7.2.1 基础用法
python 复制代码
import streamlit as st

# 定义一个片段函数
@st.fragment
def my_fragment():
    if st.button("点我(仅重运行此片段)"):
        st.success("片段被重新执行了!")

# 调用片段函数
my_fragment()
7.2.2 在侧边栏或其他容器中使用

可以将片段函数放在 st.sidebarst.columns 等容器中,实现局部重运行:

python 复制代码
import streamlit as st

@st.fragment
def sidebar_fragment():
    st.selectbox("选项", ["A", "B", "C"])
    st.slider("数值", 0, 100)

# 将片段放入侧边栏
with st.sidebar:
    sidebar_fragment()

7.3 片段的执行流程

当用户与片段内的组件交互时,执行流程如下:

  1. 片段重运行:仅重新执行片段函数内的代码,应用的其他部分保持不变
  2. 局部更新:片段内的组件会被重绘,而片段外的组件不会受影响
  3. 非片段组件交互:如果用户与片段外的组件交互,仍会触发完整的脚本重运行
多片段示例
  • 点击片段内的组件:仅该片段重运行,其他部分不变
  • 点击主应用中的"更新"按钮:触发完整脚本重运行,所有组件都会重绘
python 复制代码
import streamlit as st

st.title("片段执行演示")

# 片段1:切换按钮和文本区域
@st.fragment
def toggle_and_text():
    col1, col2 = st.columns(2)
    col1.toggle("切换开关")
    col2.text_area("输入文本")

# 片段2:筛选和文件上传
@st.fragment
def filter_and_upload():
    col1, col2 = st.columns(2)
    col1.checkbox("启用筛选")
    col2.file_uploader("上传图片")

# 主应用代码
toggle_and_text()
col1, col2 = st.columns(2)
col1.selectbox("选择", [1, 2, 3])
col2.button("更新(触发全页重运行)")
filter_and_upload()

7.4 片段的关键特性

  1. 与会话状态交互 :片段函数可以读写 st.session_state,这是片段与应用其他部分共享数据的推荐方式

  2. 自动重运行(流模式) :通过 run_every 参数,可以让片段按指定时间间隔自动重运行,无需用户交互

    python 复制代码
    @st.fragment(run_every="10s")
    def auto_update():
        df = get_latest_data()  # 获取最新数据
        st.line_chart(df)  # 每10秒自动更新图表
  3. 从片段内触发全页重运行 :在片段函数内调用 st.rerun(),可以触发整个脚本重运行

7.5 片段与其他功能的对比

功能 核心区别 适用场景
片段(Fragments) 局部重运行,交互时立即处理,可实时更新 单组件更新、实时数据、局部筛选
表单(Forms) 批量提交,仅提交时处理,无实时更新 多参数配置、批量提交场景
回调(Callbacks) 脚本重运行前执行,无法控制重运行范围 简单交互逻辑、状态同步
缓存(Caching) 跳过重复计算,不改变重运行范围 数据加载、模型推理等耗时操作

7.6 注意事项与限制

  • 返回值不推荐 :Streamlit 在片段重运行时会忽略函数返回值,建议通过 st.session_state 共享数据
  • 容器行为差异
    • 片段主容器内的元素:重运行时会被清除并重绘
    • 片段外的容器元素:会累积显示,直到下一次全页重运行,可使用 st.empty() 避免元素累积
  • 输入输出限制 :片段函数无法直接检测输入值的变化,建议使用 st.session_state 管理状态
  • 嵌套限制:片段函数不支持嵌套使用,且片段内无法定义新的片段函数

8. 组件行为(Widget Behavior)

组件(如 st.buttonst.selectboxst.text_input)是 Streamlit 应用的核心交互元素,它们将用户输入传递给 Python 代码。理解组件的工作原理、生命周期和关键行为,是避免异常、实现复杂交互的基础。

8.1 组件的核心组成

每个 Streamlit 组件都由四部分构成:

  1. 前端界面:用户在浏览器中看到的交互控件(如按钮、输入框)
  2. 后端状态:Python 内存中维护的组件状态
  3. 会话状态映射st.session_state 中与组件值关联的键值对
  4. 返回值 :组件函数执行时返回的当前值(如 st.button 返回布尔值)

8.2 组件身份与唯一标识

Streamlit 依靠组件身份(Widget Identity)来识别和维护组件状态,分为两种模式:

8.2.1 基于 Key 的标识(推荐)

当组件指定了 key 参数时,key 就是组件身份的主要决定因素。

  • 优势:修改标签、占位符等参数时,组件状态不会被重置
  • 例外 :修改 min_value/max_value(如滑块)或 options(如下拉框)等约束性参数时,即使指定了 key,组件仍可能被重置
8.2.2 基于参数的标识

未指定 key 时,组件身份由其参数(labeloptionsmin/max 等)共同决定。

  • 问题 :任何参数变化(如修改 label 文本)都会被 Streamlit 视为新组件,导致状态重置
8.2.3 避免重复组件错误

同一页面上,两个同类型组件若参数完全相同(且未指定 key),会抛出 DuplicateWidgetID 错误。解决方法是为它们指定不同的 key

python 复制代码
# ❌ 错误示例:两个相同按钮,无唯一标识
st.button("OK")
st.button("OK")

# ✅ 正确示例:指定唯一 key
st.button("OK", key="privacy_btn")
st.button("OK", key="terms_btn")

8.3 组件交互的执行顺序

当用户与组件交互时,Streamlit 会按以下顺序执行:

  1. 更新 st.session_state 中对应的组件值
  2. 执行组件的回调函数(on_change/on_click
  3. 重新运行整个脚本,组件函数返回更新后的值

注意:回调函数在脚本重运行前执行,因此回调中不应调用其他组件函数,否则内容会在下次重运行时消失。

8.4 组件的状态保持与重置

组件的状态(用户输入的值)能否被保留,取决于其身份是否保持不变:

  • 身份不变:状态会被保留,用户输入的值会被记住
  • 身份变化:Streamlit 会将其视为新组件,状态会被重置为默认值

场景示例:动态参数的滑块

当滑块的 min/max 值由其他输入控制时,修改 min/max 会导致滑块身份变化,状态重置。

python 复制代码
import streamlit as st

col1, col2 = st.columns(2)
max_val = col1.number_input("最大值", 1, 10, 7)
min_val = col2.number_input("最小值", 1, 7, 5)

# ❌ 无 key,修改 min/max 会重置状态
st.slider("无 key 滑块", min_val, max_val, value=7)

# ✅ 有 key,配合会话状态维护值,避免重置
if "slider_val" not in st.session_state:
    st.session_state.slider_val = 7

def update_slider():
    # 确保值在新的 min/max 范围内
    st.session_state.slider_val = max(min(st.session_state.slider_val, max_val), min_val)

st.slider("有 key 滑块", min_val, max_val, key="slider_val", on_change=update_slider)

8.5 组件生命周期与状态持久化

  1. 会话级持久化:组件状态仅存在于当前浏览器标签页会话中,关闭标签页或切换页面后,未被会话状态显式保存的状态会被清除

  2. 跨页面状态保存 :若需要在页面间保留组件值,需将其显式复制到独立的会话状态变量中

    python 复制代码
    # 保存组件值到独立会话状态变量,跨页面保留
    st.number_input("输入值", key="temp_val", on_change=lambda: st.session_state.update({"persist_val": st.session_state.temp_val}))
  3. 清理机制:脚本运行结束或页面切换时,Streamlit 会清理当前页面未显示组件的状态

8.6 组件与URL参数绑定(Query Params)

Streamlit 1.55.0 及以上版本支持通过 bind="query-params" 参数,将组件值同步到 URL 查询参数中:

python 复制代码
import streamlit as st

# 将下拉框的值同步到 URL 的 color 参数
st.selectbox("颜色选择", ["Red", "Green", "Blue"], key="color", bind="query-params")
  • 效果 :选择 "Green" 后,URL 会变为 ?color=Green,用户可分享、收藏该链接,直接打开即可恢复之前的选择
  • 特性:默认值对应的参数不会出现在 URL 中,保持链接简洁;无效值会被忽略

8.7 关键使用注意事项

  1. 多页面应用 :使用 st.navigation 时,公共组件应放在入口文件中,避免页面切换时的状态问题
  2. 回调与组件值 :回调函数中直接访问组件值时,应通过 st.session_state 获取,而非传递参数
  3. 隐藏组件:临时隐藏组件会导致其状态被重置,如需保留状态,应使用会话状态显式存储
相关推荐
m0_470857643 小时前
PHP怎么实现工厂模式_Factory模式编写指南【指南】
jvm·数据库·python
大数据魔法师3 小时前
Streamlit(三)- Streamlit 多页面应用开发
python·web
我的xiaodoujiao3 小时前
API 接口自动化测试详细图文教程学习系列20--结合Pytest框架使用
python·学习·测试工具·pytest
python在学ing3 小时前
前端-CSS学习笔记
前端·css·python·学习
IT策士4 小时前
Django 从 0 到 1 打造完整电商平台:为什么用 Django 做电商?
后端·python·django
zkkkkkkkkkkkkk4 小时前
Linux进行管理工具Supervisor配置与使用
linux·python·supervisor
2301_783848654 小时前
mysql数据库迁移到云平台流程_使用数据传输服务DTS工具
jvm·数据库·python
开发者联盟league4 小时前
linux普通用户使用pip安装模块
linux·python·pip
老纪5 小时前
如何解决OUI图形界面无法调用_xhost与DISPLAY变量设置
jvm·数据库·python