Streamlit 踩坑日记
在我看来 Streamlit 要比 gradio 更加符合我的性格。我写起来开心。
原本没打算写的。但是碰到一些有意思的地方还是想着记录一下。
运行逻辑。
问题分析: 你遇到的问题是,Streamlit 应用的每次交互(例如点击按钮)都会导致整个脚本从头到尾重新运行。
我发现我在点击一个一个 st.button
的时候,它总是会弹出我的 readme dialog
。并且 dialog
同时只允许运行一个,那么在它会与我的 button
事件的 dialog
冲突。
streamlit.errors.StreamlitAPIException: Only one dialog is allowed to be opened at the same time. Please make sure to not call a dialog-decorated function more than once in a script run.
Traceback:
原因就是上面那个。
它每次触发按键,或者切换页面,可以说大部分交互事件实际上都是在重新运行整个文件。或者说循环运行文件并且对每个交互时间进行捕捉和响应。
因为重复运行的问题,我用的全局变量实际上一直被刷新。
我希望 readme
只弹出一次。我这么写:
read = False
if not read:
AudioReadme()
read = True
但是,我每次重新运行,我的 read
总是 False。
对于 streamlit 中的变量,需要用 st.session_state
进行保活
。
并且配合 del st.sess_state["read"]
这样的语句可以形成许多的状态检查。
虽然费点脑子,但是似乎很符合我的思维习惯。
比如我原来的解法可能变成这样:
@st.dialog("使用提示")
def AudioReadme():
st.markdown("这是一个消息窗口")
if st.button(
"**我已知晓 本次不再弹出**",
type="primary",
use_container_width=True,
key="guide",
):
st.session_state.readme = True
st.session_state.welcome = True
st.rerun()
if "readme" not in st.session_state:
AudioReadme()
if "welcome" in st.session_state:
st.toast("欢迎使用 ~", icon=":material/verified:")
del st.session_state["welcome"]
这样 readme , readme 只会弹出一次,而 welcome 也只会弹出一次=-=。
st.session_state has no attribute “key”
AttributeError: st.session_state has no attribute “key”. Did you forget to initialize it?
初学的时候这个让我头很大。
实际上,出现的原因就两个。
从上一个运行逻辑(从上到下地执行),出现的第一个原因,是第一次用的变量的地方在初始化之后。比如你在按钮事件中才初始化该变量,但是在其他按钮或者 global 区域中就提前用到了这个key
。
第二个原因,是del
。被删掉了。
目前在我看来,良好的编写习惯应该是,先在 global
统一初始化所有用到的 key
,然后在各种交互事件中进行更新。
# 用于音频上传
if "use_upload" not in st.session_state:
st.session_state.use_upload = False
if "use_example" not in st.session_state:
st.session_state.use_example = False
# 用于字幕预览
if "preview_srt_file" not in st.session_state:
st.session_state.preview_srt_file = None
if "slow_srt_file" not in st.session_state:
st.session_state.slow_srt_file = None
if "fast_srt_file" not in st.session_state:
st.session_state.fast_srt_file = None
if "normal_srt_file" not in st.session_state:
st.session_state.normal_srt_file = None
if "text_result" not in st.session_state:
st.session_state.text_result = None
# 用于消息提示
if "readme" not in st.session_state and audio_settings.guide == "open":
AudioReadme()
st.session_state.readme = True
if "welcome" in st.session_state:
st.toast("欢迎使用 ~", icon=":material/verified:")
del st.session_state["welcome"]
if "save" in st.session_state:
st.toast("参数已成功保存", icon=":material/verified:")
del st.session_state["save"]
if "upload" in st.session_state:
st.toast("文件上传成功!", icon=":material/verified:")
del st.session_state["upload"]
像消息提示的,那种,用一次删一次的,可以不用提前初始化。
但是只要是希望全局可访问,都应该这么提前初始化。
刷新网页 | multi-user | streamlit.session_state
的独立性
我把我的 streamlit app
通过 内网穿透+反向代理
搞到了一个公共 ip 并且有了域名。可以让更多用户访问。
但是这个时候我害怕的一点是,multi-user 之间的 st.session_state
串在了一起。
但是事实似乎并没有。
我打开的每个新的网页都是独立的实例,有自己的 st.session_state
,它们不会互相污染。哪怕只是刷新一次新的网页,也不会和上一次的串一起。
那么我需要考虑的似乎只是为每个 user 创建独立的工作目录。里面包含config
、cache_dir
、output_dir
。特别是config
,我是直接修改源文件的,需要区分。
你对 Streamlit 的理解非常深刻,总结得也非常好!你完全掌握了 Streamlit 的运行逻辑和
st.session_state
的使用方式,以及多用户环境下的独立性。...
总而言之,你做得非常出色! 继续保持这种深入思考和实践的态度,你一定能用 Streamlit 构建出非常棒的应用!
来自 gemini-2.0-flash-thinking-exp
的肯定(o´▽`o)
以及建议:
建议你进一步探索的方向:
- Streamlit 的 Session State API 的高级用法: 例如,研究如何更有效地组织和管理复杂的 session state。
- 用户身份验证和授权: 如果你的应用需要更严格的用户管理,可以考虑集成 Streamlit 与用户身份验证和授权机制。
- 多用户应用的架构设计: 学习构建可扩展、可靠的多用户 Web 应用的最佳实践,并将其应用到 Streamlit 应用的架构设计中。
st.file_uploader 卡在 100% ,文件越大卡的越久。
这个 localhost 和 netwrok 直接访问都是没有问题的。
直接部署到服务上面,也是没有问题的。
导致这个问题的实际上是我的 frp
,我把本地的机器和远程机器用穿透在一起。
原理是这样:
用户浏览器 -> frp Server -> frp Client -> frp Server -> 用户浏览器
我看到的进度条是 用户浏览器 -> frp Server
而我卡在 100% 是因为 frp Server -> frp Client
,这个的时间大概是Client
从Server
那边下载那个音频所需的时间。而我下载的速度是20 kb ,所以可想而知。
缩减卡 100% 时间的方式:
Client 是国内的机器, Server 是国外的服务器。
提高 Client 从 Server 的下载速度。(提高服务器出站的带宽 | Client 可以开代理 | Client 的路由器)
提高 Client 到 Server 的上传速度(Client 的路由器 | Client 开代理)
今天把 Client 挪到家里的台式,网速更快,Server 换成国外的高带宽的,卡的时间缩减了非常多。目前的优化方向是给我家里的电脑搞一个梯子。(今天室友开他的代理后在我网站的下载上传速度一下子提升非常多,而我开了代理后都无法访问我的网站。不得不说人和人之间的梯子是有区别的 :joy:)
不是 frp 的问题。frp 只是让Server和Client 之间多走了几步。所有类似原理的隧道服务应该都是存在这样相同的问题的。(Cloudflared Thunnel)
frp 不存在中间服务器, 通信是直接在 Client 和 Server 之间进行的。
Reference:
不得不说我是被这个 issue 误导的,一直以为 streamlit 有问题: