Streamlit部署后会话共享致模拟结果重复问题求助
问题:Streamlit棒球模拟应用重复返回首次模拟结果
我用Python开发了Streamlit棒球模拟应用Ball.Sim(Beta),已部署上线。但遇到异常:首次启动选择两支球队运行模拟时一切正常,但更换其他球队再次点击“Go”按钮后,模拟结果仍与首次一致,点击下载Play By Play数据按钮,得到的始终是首次模拟的球员数据。即使在其他设备上运行该应用,也会出现相同问题。我期望更换球队后应用能重新执行全部模拟流程,原以为这是默认行为。附上云端运行的主app.py文件及GitHub仓库(MLB-Simapp),希望得到技术帮助。
原app.py代码
import sqlite3 import streamlit as st from objects import Team import game_functions from streamlit_option_menu import option_menu import pandas as pd import numpy as np def convert_df(df): return df.to_csv().encode("utf-8") # title and stuff st.set_page_config(page_title = 'Ball.Sim (Beta)', page_icon = '⚾️') st.title('Ball.Sim (Beta)') st.write('\nCreated by Jensen Holm') st.write('Data Source: Sports-Reference') st.write('\nDonate') # get rid of streamlit option menu stuff # hide_streamlit_style = """ # <style> # #MainMenu {visibility: hidden;} # footer {visibility: hidden;} # </style> # """ # st.markdown(hide_streamlit_style, unsafe_allow_html=True) # import the player data dict dictionaries for the keys so we can have options in the select box sim_data_db = sqlite3.connect("Sim_Data.db") # get list of all table names with the cursor to put into the st.select_box all_teams_array = np.array(pd.read_sql_query("SELECT name FROM sqlite_master WHERE type='table'", sim_data_db)) # the all teams array is nested so lets unnest it all_teams_with_year = [] for sublist in all_teams_array: for thing in sublist: all_teams_with_year.append(thing) all_teams = [team[len('Year '):] for team in all_teams_with_year] all_teams.sort() all_teams.insert(0, "Start typing and select team") # user unput buttons and sliders team1 = st.selectbox(label = "Team 1", options = all_teams) team2 = st.selectbox(label = "Team 2", options = all_teams) number_of_simulations = st.slider("Number of Simulations", min_value = 162, max_value = 16200, step = 162) # initialize simulation button init_button = st.button("Go") if init_button: if team1 == "Start typing and select team" or team2 == "Start typing and select team": st.error("Must select team from select boxes.") st.stop() # select databse tables based on user input team1_data = pd.read_sql_query(f"SELECT * FROM 'Year {team1}'", con = sim_data_db) team2_data = pd.read_sql_query(f"SELECT * FROM 'Year {team2}'", con = sim_data_db) team1_year = team1[:5] team2_year = team1[:5] team1_name = team1[5:] team2_name = team2[5:] # generate teams with the data queried above Team1 = Team(team1_name, team1_year, team1_data, lineup_settings = 'auto') Team2 = Team(team2_name, team2_year, team2_data, lineup_settings = 'auto') # begin simulation pbp_df = game_functions.simulation(number_of_simulations, Team1, Team2, 0) # make it a csv and use st.download_button() pbp_csv = convert_df(pbp_df) st.download_button("Download Play By Play Data", pbp_csv, file_name = "BallSimPBP.csv") # use st.tabs to filter what to see, like a chart tab for the graph, a place where they can view stats per 162 for each player # and download data and stuff on another one (may need to figure out the st.session state thing tho) # footer type stuff, plugging myself. st.write(f'\n\nFeedback and report bugs: holmj@mail.gvsu.edu') st.write('\nSocials: Twitter GitHub Linkedin') st.write('Documentation / Code')
解决方案
问题根源
- Streamlit会话状态未管理:
st.download_button的内容仅在首次点击“Go”时生成,页面重新运行后(更换球队再点击),下载按钮仍绑定旧的pbp_csv变量,因为Streamlit会缓存组件状态。 - 全局数据库连接缓存:全局的
sqlite3.connect可能被Streamlit的缓存机制保留,导致查询数据未更新。 - 代码逻辑错误:
team2_year = team1[:5]写错了,导致Team2始终使用Team1的年份数据,影响模拟准确性。
修改步骤
- 用
st.session_state管理模拟结果:将生成的pbp_csv存入会话状态,确保每次点击“Go”后更新。 - 调整数据库连接方式:将连接移到按钮点击内部,或用
@st.cache_resource装饰连接函数,避免全局缓存。 - 修正年份赋值错误:将
team2_year = team1[:5]改为team2_year = team2[:5]。
修改后的核心代码
# 初始化会话状态,存储下载数据 if 'pbp_csv' not in st.session_state: st.session_state.pbp_csv = None # 用户输入部分保持不变 team1 = st.selectbox(label = "Team 1", options = all_teams) team2 = st.selectbox(label = "Team 2", options = all_teams) number_of_simulations = st.slider("Number of Simulations", min_value = 162, max_value = 16200, step = 162) # 模拟按钮逻辑 init_button = st.button("Go") if init_button: if team1 == "Start typing and select team" or team2 == "Start typing and select team": st.error("Must select team from select boxes.") st.stop() # 每次点击都重新建立数据库连接,避免缓存 with sqlite3.connect("Sim_Data.db") as sim_data_db: team1_data = pd.read_sql_query(f"SELECT * FROM 'Year {team1}'", con = sim_data_db) team2_data = pd.read_sql_query(f"SELECT * FROM 'Year {team2}'", con = sim_data_db) # 修正Team2年份的错误 team1_year = team1[:5] team2_year = team2[:5] team1_name = team1[5:] team2_name = team2[5:] Team1 = Team(team1_name, team1_year, team1_data, lineup_settings = 'auto') Team2 = Team(team2_name, team2_year, team2_data, lineup_settings = 'auto') pbp_df = game_functions.simulation(number_of_simulations, Team1, Team2, 0) pbp_csv = convert_df(pbp_df) # 更新会话状态中的下载数据 st.session_state.pbp_csv = pbp_csv # 下载按钮绑定会话状态中的数据 if st.session_state.pbp_csv is not None: st.download_button("Download Play By Play Data", st.session_state.pbp_csv, file_name = "BallSimPBP.csv")
额外优化建议
- 对
game_functions.simulation函数添加缓存控制,如果模拟逻辑依赖输入参数,可使用@st.cache_data(针对数据输出)或@st.cache_resource(针对对象),但需确保参数变化时缓存失效。 - 可以添加加载状态,用
st.spinner()提示用户模拟正在进行。
内容的提问来源于stack exchange,提问作者Jensen Holm
相关产品推荐
相关产品推荐

