關(guān)于Streamlit性能優(yōu)化:緩存與狀態(tài)管理實戰(zhàn)
Streamlit性能優(yōu)化:緩存與狀態(tài)管理
Streamlit 是一個開源的 Python 庫,專為快速構(gòu)建數(shù)據(jù)科學(xué)和機器學(xué)習(xí) Web 應(yīng)用而設(shè)計。它無需前端開發(fā)經(jīng)驗,通過簡單 API 即可創(chuàng)建交互式界面,適合原型開發(fā)和數(shù)據(jù)展示
Streamlit官方地址:Streamlit • A faster way to build and share data apps
核心特性
- 極簡代碼:用純 Python 實現(xiàn)界面交互
- 實時預(yù)覽:保存代碼后自動刷新頁面
- 豐富組件:支持圖表、表格、滑塊、文件上傳等
- 無縫集成:兼容 Pandas、Matplotlib、PyTorch 等主流庫
安裝Streamlit
pip3 install streamlit
先通過一個簡單的Hello World案例來了解Streamlit
import streamlit as st
# 顯示標(biāo)題
st.title("Hello World,I'm echola")
# 顯示文本
st.write("這是一個由Streamlit搭建的Web平臺")運行:
streamlit run hello.py
結(jié)果:

是不是很強悍,三行代碼搞定一個Web應(yīng)用
運行原理
Streamlit 的運行邏輯圍繞腳本的線性執(zhí)行和響應(yīng)式更新展開,其核心設(shè)計是讓開發(fā)者以極簡的方式構(gòu)建交互式應(yīng)用。以下是關(guān)鍵邏輯分步解析:
1、啟動Web服務(wù)器
- Streamlit 啟動一個本地 Web 服務(wù)器,默認(rèn)監(jiān)聽 8501 端口
- 打開瀏覽器并導(dǎo)航到 http://localhost:8501,展示應(yīng)用界面
2、解析和執(zhí)行腳本:
- Streamlit 解析 hello.py 文件,生成抽象語法樹(AST)
- 動態(tài)執(zhí)行腳本中的代碼,按照順序執(zhí)行每個 Streamlit 組件(如 st.title 和 st.write)
3、組件渲染
- 每個 Streamlit 組件(如 st.title 和 st.write)會被注冊到當(dāng)前頁面的狀態(tài)中
- 頁面會根據(jù)組件的順序和內(nèi)容進行渲染
4、實時更新:
- 基于Websocket通信:瀏覽器與服務(wù)器保持長連接,腳本輸出的文本、圖表等實時推送至前端
- 增量更新機制:Streamlit只能對比前后兩次執(zhí)行的輸出差異,僅向瀏覽器發(fā)送差異部分,也就是只更新變化的部分(而非刷新整個頁面),Streamlit 會自動重新運行政整個腳本(而非局部更新)并更新頁面,確保了開發(fā)過程中的高效性和實時性
上述增量更新可能會有一點矛盾,簡而言之就是,「全腳本執(zhí)行 + 差異更新」的設(shè)計,讓 Streamlit 在開發(fā)便捷性(無需手動管理更新)和運行效率(局部渲染)之間取得了完美平衡
(1)全腳本執(zhí)行
??:也要避免全局作用域的冗余計算(需用緩存優(yōu)化)
下來使用一個簡單的案例,來模擬Streamlit加載全腳本的耗時過程
import time
import streamlit as st
# 全腳本執(zhí)行部分:以下代碼每次交互都會運行
st.title("TimeOut Example") # ? 標(biāo)題會重復(fù)渲染,但 Streamlit 會優(yōu)化為"增量更新"
# 局部增量執(zhí)行:以下代碼僅在按鈕點擊時觸發(fā)
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!")當(dāng)用戶點擊按鈕時,觸發(fā) if 條件判斷,顯示加載提示框 "Loading..."。開始模擬耗時操作,通過循環(huán)和 time.sleep 模擬耗時。每次循環(huán)中,更新進度條的值,進度條從0%逐漸增加到100%

直至耗時完成5s后,隱藏加載提示框,顯示成功消息框”Loading completed“

再次點擊【Click me】, 重復(fù)上述效果圖
可以從上述效果中看出,無論是頁面首次加載、按鈕點擊,還是其他組件交互(如下拉框選擇),Streamlit都會從頭到尾重新執(zhí)行整個腳本
雖然腳本會全量執(zhí)行,但Streamlit內(nèi)部通過智能的組件狀態(tài)管理和緩存機制,只更頁面中發(fā)生變化的部分(如按鈕觸發(fā)的進度條),而不是刷新整個頁面
接下來會使用緩存機制進行優(yōu)化
(2)差異更新
可以高效渲染(減少網(wǎng)絡(luò)傳輸數(shù)據(jù)量和瀏覽器渲染開銷)和無縫體驗(用戶輸入狀態(tài),如:文本框焦點、滾動條位置,不會因為局部更新而丟失)
??:也要關(guān)注復(fù)雜UI的組件鍵(Key)的穩(wěn)定性
緩存機制
為什么使用緩存?
問題:每次點擊click按鈕時,代碼會從執(zhí)行整個耗時操作(for循環(huán)+time.sleep),即使操作結(jié)果不變
緩存的作用:將耗時操作的結(jié)果緩存起來,后續(xù)重復(fù)調(diào)用時直接讀取緩存,避免重復(fù)計算
解決重復(fù)計算問題:通過裝飾器@st.cache_data(緩存數(shù)據(jù))或@st.cache_resource(緩存資源如模型、數(shù)據(jù)庫連接),避免腳本執(zhí)行導(dǎo)致的重復(fù)計算
@st.cache_data
def heavy_computation():
# 此函數(shù)僅在輸入?yún)?shù)或代碼變更時重新執(zhí)行
return result使用@st.cache_data的優(yōu)化方案
那優(yōu)化一下上面提到的問題
import time
import streamlit as st
st.title("Optimize Example")
# 緩存耗時操作的結(jié)束(假設(shè)操作是無參數(shù))
@st.cache_data
def expensive_operation():
# 模擬耗時操作(例如:數(shù)據(jù)計算)
result = []
for _ in range(100):
time.sleep(0.05) # 假設(shè)這是實際的計算步驟
result.append(_) # 模擬中間結(jié)果
return result
if st.button("Click me"):
processing_bar = st.progress(0) # 每次點擊時新建進度條
with st.spinner("Loading..."):
# 獲取數(shù)據(jù)(首次點擊執(zhí)行耗時操作,后續(xù)點擊直接讀緩存)
data = expensive_operation()
for percent_complete in range(len(data)):
processing_bar.progress(percent_complete + 1)
st.success("Loading completed!")首次點擊【Click me】,會出現(xiàn)

大概5s后,執(zhí)行完成

重復(fù)點擊【Click me】 ,不會重復(fù)加載進度條,由于直接讀取緩存結(jié)果,無需重復(fù)計算,數(shù)據(jù)已緩存,進度條會快速更新到100%

通過 @st.cache_data 裝飾器緩存耗時操作的結(jié)果,避免每次點擊按鈕時都重新執(zhí)行耗時操作
不是所有耗時操作都必須使用緩存
緩存適用場景
需要緩存的場景:
- 耗時操作的結(jié)果是 靜態(tài)的(例如讀取文件、初始化模型、復(fù)雜計算)。
- 操作結(jié)果 不依賴外部變量或用戶輸入。
不適用緩存場景:
- 操作結(jié)果 依賴動態(tài)參數(shù)(例如用戶輸入的變量),此時需通過函數(shù)參數(shù)觸發(fā)緩存更新。
- 操作需要 實時更新(例如每次點擊都需重新計算)
如果耗時操作 依賴參數(shù),可以通過函數(shù)參數(shù)控制緩存版本:
@st.cache_data
def expensive_operation(param1, param2):
# 根據(jù)參數(shù)執(zhí)行不同計算
results = []
for _ in range(100):
time.sleep(0.05)
results.append(param1 + param2 + _)
return results
# 在按鈕點擊時傳入?yún)?shù)
data = expensive_operation(10, 20) # 參數(shù)不同會生成不同緩存可以看出:
- 緩存機制:通過
@st.cache_data緩存靜態(tài)計算結(jié)果,減少重復(fù)執(zhí)行。 - 進度條優(yōu)化:將耗時操作與進度條更新分離,首次加載緩存后,后續(xù)交互可快速完成
那上述代碼就沒有什么問題了嗎?
??接下來分析原代碼存在的弊端:
- 進度條重復(fù)創(chuàng)建:每次點擊按鈕都會新建processing_bar,導(dǎo)致多次點擊時進度條堆疊
- 無法阻止重復(fù)提交:在耗時操作執(zhí)行期間,用戶仍可多次點擊按鈕,導(dǎo)致邏輯混亂
- 狀態(tài)丟失:進度完成后的狀態(tài)(如success提示)無法持久化
使用st.session_state的優(yōu)化方案
1、保存進度條實例
if "processing_bar" not in st.session_state:
st.session_state.processing_bar = None # 初始化進度條容器
if st.button("Click me"):
# 僅在第一次點擊時創(chuàng)建進度條
if not st.session_state.processing_bar:
st.session_state.processing_bar = st.progress(0)
# 后續(xù)操作復(fù)用已有進度條
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. 防止重復(fù)提交
if "is_processing" not in st.session_state:
st.session_state.is_processing = False # 狀態(tài)鎖
if st.button("Click me") and not st.session_state.is_processing:
st.session_state.is_processing = True # 鎖定
try:
# 執(zhí)行耗時操作...
finally:
st.session_state.is_processing = False # 釋放3. 持久化完成狀態(tài)
if "load_complete" not in st.session_state:
st.session_state.load_complete = False
if st.button("Click me"):
# 執(zhí)行操作...
st.session_state.load_complete = True
if st.session_state.load_complete:
st.success("數(shù)據(jù)已加載完成!")
st.balloons() # 顯示動畫效果完整優(yōu)化代碼
import time
import streamlit as st
st.title("Optimized Example")
# 初始化會話狀態(tài)
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:
# 創(chuàng)建或復(fù)用進度條
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()關(guān)鍵作用總結(jié)
| 會話狀態(tài)項 | 功能說明 |
|---|---|
| processing_bar | 保持進度條對象引用,防止重復(fù)創(chuàng)建 |
| is_processing | 實現(xiàn)類似互斥鎖,防止重復(fù)提交 |
| load_complete | 持久化完成狀態(tài),實現(xiàn)跨腳本執(zhí)行記憶 |
通過 st.session_state 實現(xiàn)了:
- 狀態(tài)持久化:在 Streamlit 的全腳本重執(zhí)行機制中保持關(guān)鍵狀態(tài)
- 資源管理:避免 DOM 元素重復(fù)創(chuàng)建
- 交互安全:防止用戶誤操作導(dǎo)致的邏輯沖突
這種模式特別適合需要保持復(fù)雜交互狀態(tài)的場景(如多步驟表單、長任務(wù)處理)
總結(jié)
通過 緩存機制 減少重復(fù)計算,結(jié)合 st.session_state 管理會話狀態(tài),Streamlit 可以高效處理復(fù)雜交互場景,同時保持代碼簡潔和用戶體驗流暢。
這種優(yōu)化策略尤其適合需要頻繁交互、狀態(tài)保持或耗時操作的 Web 應(yīng)用開發(fā)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
pandas DataFrame實現(xiàn)幾列數(shù)據(jù)合并成為新的一列方法
今天小編就為大家分享一篇pandas DataFrame實現(xiàn)幾列數(shù)據(jù)合并成為新的一列方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06
使用Python實現(xiàn)租車計費系統(tǒng)的兩種方法
本文通過兩種方法給大家分享了使用Python實現(xiàn)租車計費系統(tǒng),非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-09-09
python3中的logging記錄日志實現(xiàn)過程及封裝成類的操作
這篇文章主要介紹了python3中的logging記錄日志實現(xiàn)過程及封裝成類的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05
詳解numpy矩陣的創(chuàng)建與數(shù)據(jù)類型
這篇文章主要介紹了詳解numpy矩陣的創(chuàng)建與數(shù)據(jù)類型,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10

