12人参与 • 2025-04-23 • 缓存
streamlit 是一个开源的 python 库,专为快速构建数据科学和机器学习 web 应用而设计。它无需前端开发经验,通过简单 api 即可创建交互式界面,适合原型开发和数据展示
streamlit官方地址:streamlit • a faster way to build and share data apps
安装streamlit
pip3 install streamlit
先通过一个简单的hello world案例来了解streamlit
import streamlit as st # 显示标题 st.title("hello world,i'm echola") # 显示文本 st.write("这是一个由streamlit搭建的web平台")
运行:
streamlit run hello.py
结果:
是不是很强悍,三行代码搞定一个web应用
streamlit 的运行逻辑围绕脚本的线性执行和响应式更新展开,其核心设计是让开发者以极简的方式构建交互式应用。以下是关键逻辑分步解析:
1、启动web服务器
2、解析和执行脚本:
3、组件渲染
4、实时更新:
上述增量更新可能会有一点矛盾,简而言之就是,「全脚本执行 + 差异更新」的设计,让 streamlit 在开发便捷性(无需手动管理更新)和运行效率(局部渲染)之间取得了完美平衡
⚠️:也要避免全局作用域的冗余计算(需用缓存优化)
下来使用一个简单的案例,来模拟streamlit加载全脚本的耗时过程
import time import streamlit as st # 全脚本执行部分:以下代码每次交互都会运行 st.title("timeout example") # ✅ 标题会重复渲染,但 streamlit 会优化为"增量更新" # 局部增量执行:以下代码仅在按钮点击时触发 if st.button("click me"): processing_bar = st.progress(0) # 每次点击时新建进度条 with st.spinner("loading..."): for percent_complete in range(100): time.sleep(0.05) processing_bar.progress(percent_complete + 1) st.success("loading completed!")
当用户点击按钮时,触发 if 条件判断,显示加载提示框 "loading..."。开始模拟耗时操作,通过循环和 time.sleep 模拟耗时。每次循环中,更新进度条的值,进度条从0%逐渐增加到100%
直至耗时完成5s后,隐藏加载提示框,显示成功消息框”loading completed“
再次点击【click me】, 重复上述效果图
可以从上述效果中看出,无论是页面首次加载、按钮点击,还是其他组件交互(如下拉框选择),streamlit都会从头到尾重新执行整个脚本
虽然脚本会全量执行,但streamlit内部通过智能的组件状态管理和缓存机制,只更页面中发生变化的部分(如按钮触发的进度条),而不是刷新整个页面
接下来会使用缓存机制进行优化
可以高效渲染(减少网络传输数据量和浏览器渲染开销)和无缝体验(用户输入状态,如:文本框焦点、滚动条位置,不会因为局部更新而丢失)
⚠️:也要关注复杂ui的组件键(key)的稳定性
问题:每次点击click按钮时,代码会从执行整个耗时操作(for循环+time.sleep),即使操作结果不变
缓存的作用:将耗时操作的结果缓存起来,后续重复调用时直接读取缓存,避免重复计算
解决重复计算问题:通过装饰器@st.cache_data(缓存数据)或@st.cache_resource(缓存资源如模型、数据库连接),避免脚本执行导致的重复计算
@st.cache_data def heavy_computation(): # 此函数仅在输入参数或代码变更时重新执行 return result
那优化一下上面提到的问题
import time import streamlit as st st.title("optimize example") # 缓存耗时操作的结束(假设操作是无参数) @st.cache_data def expensive_operation(): # 模拟耗时操作(例如:数据计算) result = [] for _ in range(100): time.sleep(0.05) # 假设这是实际的计算步骤 result.append(_) # 模拟中间结果 return result if st.button("click me"): processing_bar = st.progress(0) # 每次点击时新建进度条 with st.spinner("loading..."): # 获取数据(首次点击执行耗时操作,后续点击直接读缓存) data = expensive_operation() for percent_complete in range(len(data)): processing_bar.progress(percent_complete + 1) st.success("loading completed!")
首次点击【click me】,会出现
大概5s后,执行完成
重复点击【click me】 ,不会重复加载进度条,由于直接读取缓存结果,无需重复计算,数据已缓存,进度条会快速更新到100%
通过 @st.cache_data 装饰器缓存耗时操作的结果,避免每次点击按钮时都重新执行耗时操作
不是所有耗时操作都必须使用缓存
需要缓存的场景:
不适用缓存场景:
如果耗时操作 依赖参数,可以通过函数参数控制缓存版本:
@st.cache_data def expensive_operation(param1, param2): # 根据参数执行不同计算 results = [] for _ in range(100): time.sleep(0.05) results.append(param1 + param2 + _) return results # 在按钮点击时传入参数 data = expensive_operation(10, 20) # 参数不同会生成不同缓存
可以看出:
@st.cache_data
缓存静态计算结果,减少重复执行。那上述代码就没有什么问题了吗?
⚠️接下来分析原代码存在的弊端:
1、保存进度条实例
if "processing_bar" not in st.session_state: st.session_state.processing_bar = none # 初始化进度条容器 if st.button("click me"): # 仅在第一次点击时创建进度条 if not st.session_state.processing_bar: st.session_state.processing_bar = st.progress(0) # 后续操作复用已有进度条 with st.spinner("loading..."): data = expensive_operation() for i in range(len(data)): st.session_state.processing_bar.progress(i + 1) # 完成后清空引用 st.session_state.processing_bar = none st.success("done!")
2. 防止重复提交
if "is_processing" not in st.session_state: st.session_state.is_processing = false # 状态锁 if st.button("click me") and not st.session_state.is_processing: st.session_state.is_processing = true # 锁定 try: # 执行耗时操作... finally: st.session_state.is_processing = false # 释放
3. 持久化完成状态
if "load_complete" not in st.session_state: st.session_state.load_complete = false if st.button("click me"): # 执行操作... st.session_state.load_complete = true if st.session_state.load_complete: st.success("数据已加载完成!") st.balloons() # 显示动画效果
完整优化代码
import time import streamlit as st st.title("optimized example") # 初始化会话状态 if "processing_bar" not in st.session_state: st.session_state.processing_bar = none if "is_processing" not in st.session_state: st.session_state.is_processing = false if "load_complete" not in st.session_state: st.session_state.load_complete = false @st.cache_data def expensive_operation(): result = [] for _ in range(100): time.sleep(0.05) result.append(_) return result if st.button("click me") and not st.session_state.is_processing: st.session_state.is_processing = true try: # 创建或复用进度条 if not st.session_state.processing_bar: st.session_state.processing_bar = st.progress(0) with st.spinner("loading..."): data = expensive_operation() for i in range(len(data)): st.session_state.processing_bar.progress(i + 1) st.session_state.load_complete = true finally: st.session_state.is_processing = false st.session_state.processing_bar = none # 重置进度条 if st.session_state.load_complete: st.success("操作成功!") st.balloons()
关键作用总结
会话状态项 | 功能说明 |
---|---|
processing_bar | 保持进度条对象引用,防止重复创建 |
is_processing | 实现类似互斥锁,防止重复提交 |
load_complete | 持久化完成状态,实现跨脚本执行记忆 |
通过 st.session_state
实现了:
这种模式特别适合需要保持复杂交互状态的场景(如多步骤表单、长任务处理)
通过 缓存机制 减少重复计算,结合 st.session_state
管理会话状态,streamlit 可以高效处理复杂交互场景,同时保持代码简洁和用户体验流畅。
这种优化策略尤其适合需要频繁交互、状态保持或耗时操作的 web 应用开发
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论