You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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')

解决方案

问题根源

  1. Streamlit会话状态未管理:st.download_button的内容仅在首次点击“Go”时生成,页面重新运行后(更换球队再点击),下载按钮仍绑定旧的pbp_csv变量,因为Streamlit会缓存组件状态。
  2. 全局数据库连接缓存:全局的sqlite3.connect可能被Streamlit的缓存机制保留,导致查询数据未更新。
  3. 代码逻辑错误:team2_year = team1[:5]写错了,导致Team2始终使用Team1的年份数据,影响模拟准确性。

修改步骤

  1. 用st.session_state管理模拟结果:将生成的pbp_csv存入会话状态,确保每次点击“Go”后更新。
  2. 调整数据库连接方式:将连接移到按钮点击内部,或用@st.cache_resource装饰连接函数,避免全局缓存。
  3. 修正年份赋值错误:将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 20:24:23