【学习记录】Streamlit 会话状态管理及登录页面实战
Streamlit 是构建数据应用和内部工具的利器,但它的"脚本从上到下每次交互都重运行"的模式常让初学者困惑。本文将深入讲解
st.session_state的使用,并以一个医疗文档问答系统(MedRAG)为例,从最简单的模拟登录到带表单的认证登录,再到欢迎页面和聊天输入框,完整展示如何管理用户状态。最后附上所有代码和面试常见考点。
📌 目录
- 核心概念
- 基础示例:模拟登录切换
- [登录表单优化:使用 st.form 避免输入卡顿](#登录表单优化:使用 st.form 避免输入卡顿)
- 完善登录逻辑与欢迎页面
- [聊天输入框与 RAG 系统预览](#聊天输入框与 RAG 系统预览)
- 面试考点总结
- 完整最终代码
核心概念
1. st.session_state
Streamlit 的 st.session_state 是一个类似 Python 字典的全局对象,可以在不同脚本运行周期 之间持久保存变量。每次用户交互(点击按钮、输入文本、选择选项等),整个脚本会从上到下重新执行 ,普通变量会丢失,而 st.session_state 中的值得以保留。
2. 脚本重运行机制
- 用户操作 → Streamlit 通知前端 → 后端重新执行整个脚本。
- 所有普通变量重新初始化,
st.session_state则保持不变。 - 利用这个机制,我们可以通过
if分支根据session_state中的标志展示不同的界面。
基础示例:模拟登录切换
下面是一个最简单的示例:用一个布尔标志 logged_in 控制显示"已登录"还是"未登录"界面,并通过按钮切换状态。
python
import streamlit as st
if "logged_in" not in st.session_state:
st.session_state.logged_in = False
if st.session_state.logged_in:
st.success("已登录")
if st.button("登出"):
st.session_state.logged_in = False
# st.rerun() 非必须,按钮自动触发重运行
else:
st.info("未登录")
if st.button("模拟登录"):
st.session_state.logged_in = True
st.rerun()
运行效果:
- 点击"模拟登录" → 标志变为
True,脚本重运行 → 显示"已登录"和"登出"按钮。 - 点击"登出" → 标志变为
False,界面切回登录按钮。
注意:
st.rerun()可以强制立即重运行,但按钮本身已经会触发重运行,这里用于更明确地控制流程。
登录表单优化:使用 st.form 避免输入卡顿
为什么需要表单?
如果在没有表单的情况下直接使用 st.text_input 和 st.button,用户在输入框中每敲击一个字符都会触发脚本重运行 → 输入框被重新创建 → 光标闪烁、输入中断,体验极差。
解决方法 :将输入框放在 st.form 中。表单内的组件不会在每次交互时重运行脚本,只有点击表单提交按钮时才一次性收集所有输入值并触发重运行。
代码示例(硬编码验证)
python
import streamlit as st
if "logged_in" not in st.session_state:
st.session_state.logged_in = False
def login_form():
st.subheader("用户登录")
with st.form("login_form"):
username = st.text_input("用户名")
password = st.text_input("密码", type="password")
submitted = st.form_submit_button("登录")
if submitted:
# 硬编码验证(仅用于演示)
if username == "admin" and password == "123456":
st.session_state.logged_in = True
st.session_state.username = username
st.success("登录成功!正在跳转...")
st.rerun()
else:
st.error("用户名或密码错误")
if st.session_state.logged_in:
st.success("已登录")
if st.button("登出"):
st.session_state.logged_in = False
st.session_state.username = ""
st.rerun()
else:
login_form()
关键点:
st.form_submit_button是表单专用的提交按钮,点击后submitted变为True。- 提交后,脚本重运行,由于
st.session_state.logged_in已被设置为True,下一次重运行时会直接进入已登录分支,登录表单不再显示。 st.rerun()确保立即重运行,避免残留表单界面。
完善登录逻辑与欢迎页面
在登录成功的基础上,我们添加一个欢迎页面 welcome_page(),显示个性化问候,并提供简单的问答输入框(仅为后续 RAG 功能做占位)。
python
import streamlit as st
if "logged_in" not in st.session_state:
st.session_state.logged_in = False
if "username" not in st.session_state:
st.session_state.username = ""
def login_form():
st.subheader("用户登录")
with st.form("login_form"):
username = st.text_input("用户名")
password = st.text_input("密码", type="password")
submitted = st.form_submit_button("登录")
if submitted:
# 硬编码验证(生产环境请替换为安全认证)
if username == "1" and password == "1":
st.session_state.username = username
st.session_state.logged_in = True
st.success("登录成功!正在跳转...")
st.rerun()
else:
st.error("用户名或密码错误")
def welcome_page():
"""登录成功后显示的欢迎页(包含问答回显)"""
st.title(f"🏥 欢迎来到 MedRAG 系统,{st.session_state.username} 👋")
st.write("你现在已登录 MedRAG 系统。")
if st.button("登出"):
st.session_state.logged_in = False
st.session_state.username = ""
st.rerun()
st.markdown("---")
st.subheader("💬 医疗文档问答(测试版)")
user_question = st.chat_input("请输入你的问题...")
if user_question:
st.info(f"📝 你的问题是:{user_question}")
st.caption("(当前仅回显问题,RAG 功能即将上线)")
if st.session_state.logged_in:
welcome_page()
else:
login_form()
聊天输入框与 RAG 系统预览
欢迎页面中使用了 st.chat_input,这是 Streamlit 专门为对话设计的输入组件:
- 按回车键提交,输入框自动清空。
- 每次提交后脚本重运行,
user_question在本次运行中非空,下次运行恢复为None,从而实现"一次性回显"。
后续可以在此处接入后端的 FAISS 检索 + DeepSeek 生成,实现真正的医疗文档问答。
面试考点总结
Q1:st.session_state 的作用是什么?它与普通变量有何区别?
高分回答 :
st.session_state 是 Streamlit 提供的跨脚本运行周期的持久字典 。普通变量在每次用户交互后都会重新初始化,而 st.session_state 中的值会一直保留,直到显式修改或清除。这使我们能够实现用户登录状态、表单数据缓存等功能。
Q2:为什么登录表单需要使用 st.form?如果不使用会有什么问题?
高分回答 :
不使用 st.form 时,st.text_input 每次击键都会触发脚本重运行,导致输入框失去焦点、光标闪烁,用户体验差。使用 st.form 后,表单内的组件不会触发重运行,只有点击 st.form_submit_button 时才一次性提交数据并重运行脚本。这避免了输入过程中的卡顿,适合收集多字段输入。
Q3:st.form_submit_button 与普通 st.button 有什么区别?
高分回答:
st.button可以在任何地方使用,点击后立即触发重运行,但它不能与st.text_input等组件一起放在表单内实现"延迟提交"。st.form_submit_button必须 放在st.form上下文中,且一个表单只能有一个提交按钮。点击后,表单内所有组件的当前值被收集,脚本重运行时submitted为True,便于一次性处理用户输入。
Q4:登出后为什么要调用 st.rerun()?不调用会怎样?
高分回答 :
st.rerun() 强制立即重新运行脚本。如果不调用,当前运行周期会继续执行完 welcome_page() 中的其余代码(如显示"登出"按钮),但之后 Streamlit 会自动重运行一次。虽然界面最终也会切换,但中间可能会短暂残留旧界面或产生不必要的闪烁。显式调用 st.rerun() 能确保立即销毁欢迎页内容,提升响应速度。
Q5:st.chat_input 与 st.text_input + st.button 相比有什么优势?
高分回答:
st.chat_input天然支持按回车提交,输入后自动清空,非常适合连续对话。- 传统
st.text_input配合st.button需要手动清空输入框(例如通过st.session_state控制),并且每次提交后输入框内容会保留,易造成重复提交。st.chat_input的设计更符合聊天场景的用户习惯。
完整最终代码
以下为最终的 frontend.py 完整代码,可直接运行:
python
import streamlit as st
# ---------- 初始化 session_state ----------
if "logged_in" not in st.session_state:
st.session_state.logged_in = False
if "username" not in st.session_state:
st.session_state.username = ""
# ---------- 登录表单 ----------
def login_form():
st.subheader("用户登录")
with st.form("login_form"):
username = st.text_input("用户名")
password = st.text_input("密码", type="password")
submitted = st.form_submit_button("登录")
if submitted:
# 硬编码验证(仅用于开发原型,生产环境需替换为安全认证)
if username == "1" and password == "1":
st.session_state.username = username
st.session_state.logged_in = True
st.success("登录成功!正在跳转...")
st.rerun()
else:
st.error("用户名或密码错误")
# ---------- 欢迎页面 ----------
def welcome_page():
"""登录成功后显示的欢迎页(包含问答回显)"""
st.title(f"🏥 欢迎来到 MedRAG 系统,{st.session_state.username} 👋")
st.write("你现在已登录 MedRAG 系统。")
if st.button("登出"):
st.session_state.logged_in = False
st.session_state.username = ""
st.rerun()
st.markdown("---")
st.subheader("💬 医疗文档问答(测试版)")
user_question = st.chat_input("请输入你的问题...")
if user_question:
st.info(f"📝 你的问题是:{user_question}")
st.caption("(当前仅回显问题,RAG 功能即将上线)")
# ---------- 主逻辑 ----------
if st.session_state.logged_in:
welcome_page()
else:
login_form()
运行命令:
bash
streamlit run frontend.py
总结
通过本文,你学会了:
- ✅ 使用
st.session_state管理跨运行的用户状态。 - ✅ 使用
st.form和st.form_submit_button构建流畅的登录表单。 - ✅ 实现登录/登出切换,并展示个性化欢迎界面。
- ✅ 使用
st.chat_input准备后续的 RAG 问答输入。
本文为医疗文档问答系统(MedRAG)的前端奠定了基础,下一步可以接入后端检索和 LLM 生成,实现完整的智能问答功能。