首页 / 资讯中心 / 文章详情

Python开源库 Streamlit 应用案例

Python开源库 Streamlit 应用案例 ★ FEATURED ARTICLE
前言学 Streamlit 的人常常在同一个地方卡住为什么我在按钮回调里改了变量页面上却还是旧值为什么我明明只动了滑块整个页面的数据又重新加载了一遍为什么别人写的例子里到处是st.session_state和st.cache_data看着像在解决什么问题我却不知道问题在哪这些疑惑的答案只有一个而且只要抓住它Streamlit 的其余设计都变得顺理成章Streamlit 的脚本是「每次交互都从头重跑一遍」的。你拖动滑块、在输入框敲字、点一下按钮Streamlit 不是去修改页面的某个局部而是把整个脚本重新执行一次然后拿新结果去更新页面。本文就围绕这一条心智模型展开。先讲清「重跑」到底意味着什么再由它推出两个必然结果普通变量会丢所以要st.session_state重复计算会白做所以要st.cache_data。最后给一个能跑的完整例子。Streamlit 是第三方库需要安装版本与参数以官方文档为准。一、理解 Streamlit 的钥匙每次交互都从头重跑写一个最简单的 Streamlit 应用# 适用于 Python 3.8需先安装 streamlitimport streamlit as stst.title(重跑演示)name st.text_input(你的名字)st.write(你好 (name or 陌生人))运行方式是命令行执行streamlit run 你的文件名.py。打开页面后你在输入框里打字会看到输出实时跟着变。关键问题来了这段代码在页面生命周期里执行了不止一次。页面第一次加载执行一次你每敲一个字符Streamlit 就把整个脚本再执行一次。也就是说st.text_input这一行被反复调用st.write也被反复调用。你看到的页面内容永远是「最后一次完整执行」的结果。理解了这一点很多怪现象就说得通了页面上看到的所有元素都是当前这次执行按顺序输出的。你在脚本里定义的普通变量每次执行都重新诞生一次。如果你的脚本里有耗时操作读数据库、拉接口它会在每一次交互时都被执行。也正因为如此Streamlit 里没有「回调触发后修改页面」这种思路——没有页面可以修改页面只是脚本输出的一帧。二、重跑带来的第一个问题变量会丢先看看哪些东西会丢、哪些不会这个区分很重要。对象重跑后会怎样原因普通局部变量重新初始化脚本重跑赋值语句又执行了一遍控件的值自动保留Streamlit 按控件标识记住了用户的选择st.session_state里的值保留它就存在「会话」这一层跨重跑存在st.cache_data缓存的结果保留缓存存在于会话之上可跨用户跨重跑也就是说你不用为控件的值操心st.text_input(你的名字)重跑之后依然会返回上一次输入的内容。但你自己算出来的中间量重跑后就没影了。最典型的场景是计数器。下面这段是错的# 适用于 Python 3.8需先安装 streamlit❌ 反例仅示意问题import streamlit as stcount 0if st.button(加一):count 1st.write(当前次数, count)每次点按钮脚本重跑count 0先把它清成 0再加 1结果永远是 1。正确做法是把它放进st.session_state# 适用于 Python 3.8需先安装 streamlitimport streamlit as stif count not in st.session_state:st.session_state[count] 0if st.button(加一):st.session_state[count] 1st.write(当前次数, st.session_state[count])st.session_state是一个在重跑之间存活的字典官方文档的说法是「一种在重跑之间共享变量的方式针对每个用户会话」。注意st.write(st.session_state)还能直接把里面的内容全部显示出来调试时很好用。它一个重要特性是每个用户有自己的一份。两个同事同时打开这个页面各自点各自按钮计数互不干扰——这正是「会话状态」的含义。三、重跑带来的第二个问题重复计算既然每次交互都重跑那么「读一个大 CSV」「调用一次慢接口」这类操作就会被反复执行。你拖动一次滑块接口就被调一次这显然不能接受。Streamlit 的解法是缓存给函数加上st.cache_data装饰器Streamlit 会记住「同样的参数调用过」下次直接返回缓存结果不再执行函数体。# 适用于 Python 3.8需先安装 streamlitimport streamlit as stst.cache_datadef load_sales(year, region):# 这里放真实场景中耗时的读取或计算return {year: year, region: region, total: year * 10}data load_sales(2026, 华东)st.write(data)理解缓存有几个要点缓存以参数为键。参数一样就用缓存参数变了就重新算。所以要求参数是可哈希的官方文档说明若对象的哈希方法不适用回退方案还要求它能被pickle序列化。缓存的返回值是副本。官方文档明确指出st.cache_data返回的是拷贝如果你要缓存一个需要就地修改的共享资源比如数据库连接、模型对象应该用st.cache_resource而不是它。st.cache_data取代了早期的st.cache。看到老教程里的st.cache请按官方文档迁移到st.cache_data返回值数据或st.cache_resource共享资源。它还有ttl过期时间、max_entries等参数具体签名以官方文档为准。一句话重跑保证了「页面上看到的永远是最新状态」缓存保证了「最新状态不必从零算起」。两者配合才是完整的一套。四、一个完整的小应用与常用控件下面这个例子把两件工具都用上了一个带缓存的「数据加载」加上一个用会话状态维护的「浏览历史」。# 适用于 Python 3.8需先安装 streamlitimport streamlit as stst.title(销售简报)# 缓存参数相同就不重新计算st.cache_datadef load_months(year):return [{month: m, amount: (m * 37 year) % 500} for m in range(1, 13)]# 会话状态跨重跑保留的浏览历史if history not in st.session_state:st.session_state[history] []year st.selectbox(年份, [2024, 2025, 2026])rows load_months(year)threshold st.slider(只看金额大于, 0, 500, 200)shown [r for r in rows if r[amount] threshold]st.write(f共 {len(shown)} 个月份超过阈值)st.dataframe(shown)if st.button(记录这次筛选):st.session_state[history].append({year: year, threshold: threshold})st.write(筛选历史, st.session_state[history])这段代码的行为值得顺着「重跑」推一遍每次你动年份脚本重跑但load_months因为参数没变或者变了之后重新算一次并缓存不会做重复劳动每次你点「记录」脚本重跑history因为存在会话状态里追加的记录被保留了下来。常用控件与布局速览控件大致用途返回值st.text_input单行文本字符串st.text_area多行文本字符串st.selectbox下拉单选选中项st.multiselect下拉多选列表st.slider数值区间拖动数值或区间st.checkbox勾选框布尔值st.button触发一次动作布尔值仅本次重跑为真st.file_uploader上传文件类文件对象关于st.button有一个必须记住的细节它返回True只在这一帧。也就是说脚本重跑一次按钮为真的判断只在「刚刚被点击后的那次重跑」成立下一次重跑比如你又动了滑块它就回到False了。所以「点击后要做的事」必须写在if st.button(...):这一块里不能指望它一直为真。布局方面st.columns可以把页面横向切成几列st.sidebar把控件放到侧边栏st.tabs做标签页。这些都是「输出到哪里」的容器不影响重跑模型。常见坑点1. 用普通变量跨重跑保存状态❌count 0写在脚本顶层指望按钮点一次加一次。 ✅ 放进if count not in st.session_state: st.session_state[count] 0之后只读改它。2. 把耗时调用放在缓存之外❌ 模块顶层直接data requests.get(...)每次交互都重新请求。 ✅ 包成一个函数并加st.cache_data让相同参数命中缓存。3. 把st.cache_data用在共享资源上❌ 用st.cache_data缓存数据库连接、模型对象。 ✅ 这类需要共享、需要就地使用的资源用st.cache_resourcest.cache_data的返回值是副本。4. 以为st.button会一直为真❌ 在按钮判断之外还用同一个布尔值控制后续逻辑。 ✅ 点击相关的逻辑全部写在if st.button(...):块内因为它的真值只维持这一帧。5. 忘了给缓存函数参数考虑可哈希性❌ 传一个不可哈希、也无法pickle的对象进被缓存的函数运行时缓存直接失效或报错。 ✅ 只传简单、可序列化的参数复杂对象在函数内部按标识自行处理。6. 在循环里创建同名控件❌ 循环中反复st.button(确定)多个控件同名行为互相干扰。 ✅ 每个控件给出唯一的key参数把它和循环变量绑定。7. 用老教程里的st.cache❌ 照抄早期资料的st.cache语义不清。 ✅ 按官方文档换成st.cache_data数据或st.cache_resource资源。8. 拿st.write当万能调试器却看不到想看的东西❌ 只st.write变量名不看会话状态里到底有什么。 ✅ 直接st.write(st.session_state)把整个会话状态打出来一眼看清哪些键在、值是什么。总结现象真正原因对策改动一处整页重建每次交互都重跑整个脚本接受它把所有逻辑写成可重复执行的形式变量改了又变回去普通变量每次重跑重新初始化放入st.session_state慢操作被反复执行重跑时那段代码又被走了一遍用st.cache_data缓存结果共享对象被复制st.cache_data返回副本共享资源改用st.cache_resource按钮判断莫名其妙失效按钮真值只维持一帧逻辑写在if st.button(...):内Streamlit 不难难的是换脑子不要再想「我改这个页面元素」而要想「这次执行会输出什么」。把这条记住st.session_state和st.cache_data就不再是需要死记的 API而是「重跑」这个模型的必然产物。
阅读完成 · 觉得有帮助?
咨询建站