RyanNerd
라덕'Story
RyanNerd
  • 분류 전체보기 (117)
    • Study Note (80)
      • Python (3)
      • R (1)
      • Airflow (7)
      • 통계 (26)
      • Machine Learning (16)
      • Streamlit (9)
      • Django (1)
      • Deep Learning (12)
      • dbt (2)
      • SQL (3)
    • 빅데이터분석기사 (1)
      • 필기 (1)
    • Programmers (28)
      • Python (13)
      • SQL (15)
    • Project (3)
      • Django (3)
    • Mac (2)

블로그 메뉴

  • NaverBlog
  • 홈

최근 글

전체 방문자
오늘
어제
hELLO · Designed By 정상우.
RyanNerd

라덕'Story

Study Note/Streamlit

"내 앱은 왜 자꾸 까먹을까?" : Session State와 Caching 완벽 해부

2025. 12. 29. 14:55

Streamlit을 쓰다 보면 당황스러운 순간이 옵니다. 버튼을 눌렀는데, 방금 입력한 환자 정보가 사라지거나 변수가 0으로 초기화되는 현상이죠. 이는 버그가 아니라 Streamlit의 고유한 작동 방식 때문입니다.

오늘은 이 '기억 상실' 문제를 해결하는 Session State와 Caching, 그리고 Widget과의 관계를 의료 시나리오로 풀어봅니다.

 

1. Streamlit의 작동 방식: "금붕어 기억력"

공식 문서에 따르면, Streamlit의 가장 큰 특징은 Data Flow입니다.

  1. 전체 재실행 (Rerun): 사용자가 버튼을 클릭하거나, 텍스트를 입력하는 등 상호작용(Interaction)이 발생할 때마다, Streamlit은 파이썬 스크립트 전체를 맨 위에서부터 맨 아래까지 다시 실행합니다.
  2. 초기화: 스크립트가 다시 실행되니, 코드 중간에 선언한 변수(count = 0 등)는 당연히 초기값으로 돌아갑니다.

의료 비유: 의사 선생님이 환자를 볼 때마다(상호작용), 차트를 처음부터 다시 읽는 것(Rerun)과 같습니다. 차트에 기록(State)을 안 해두면, 방금 환자 이름을 물어보고도 "누구시죠?" 하고 다시 묻게 되는 셈입니다.

 

2. 해결사 1: Caching (장기 기억 장소)

Caching은 "데이터 로딩"이나 "모델 학습"처럼 오래 걸리는 작업의 결과를 저장해두는 곳입니다.

  • 역할: 데이터가 바뀌지 않았다면, 함수를 다시 실행하지 않고 저장해둔 결과를 줍니다.
  • 사용법: 함수 위에 @st.cache_data 붙이기.
import streamlit as st
import pandas as pd
import time

@st.cache_data
def load_large_medical_data():
    # 3초 걸리는 무거운 작업 가정
    time.sleep(3) 
    df = pd.DataFrame({
        'Patient_ID': [101, 102, 103],
        'Diagnosis': ['Hypertension', 'Diabetes', 'Asthma']
    })
    return df

# 처음엔 3초 걸리지만, 버튼을 눌러 앱이 Rerun 되어도 이 부분은 0초 만에 통과합니다.
df = load_large_medical_data()

 

 

3. 해결사 2: Session State (단기 기억 장소)

Session State는 사용자별로 변수를 유지(Persist)하기 위한 저장소입니다. 스크립트가 다시 실행(Rerun)되어도 여기 저장된 값은 사라지지 않습니다.

  • 역할: "현재 몇 번째 환자를 보고 있는지", "이전 입력값이 무엇이었는지" 기억합니다.
  • 구조: 파이썬의 딕셔너리(Dictionary)와 똑같습니다. (st.session_state['key'] = value)

[Code] 환자 카운터 만들기

import streamlit as st

st.title("환자 데이터 검수 카운터")

# 1. 초기화: 'count'라는 방이 없으면 0으로 만듭니다.
if 'count' not in st.session_state:
    st.session_state.count = 0

# 2. 기능 구현: 버튼을 누르면 State 값을 증가시킵니다.
# 버튼을 누르면 앱이 Rerun 되지만, st.session_state.count 값은 유지/증가됩니다.
increment = st.button('검수 완료 (다음 환자)')
if increment:
    st.session_state.count += 1

st.write(f"현재까지 검수한 환자 수: {st.session_state.count}명")

 

4. 위젯(Widget)과 Session State의 관계

Streamlit의 입력 위젯(st.number_input, st.text_input 등)은 Session State와 밀접하게 연결되어 있습니다.

핵심 연결 고리: key 파라미터와 Callback

  • key: 위젯에 key="my_input"이라고 이름을 붙이면, st.session_state["my_input"]에 그 값이 자동으로 연결됩니다.
  • Callback (on_change, on_click): 위젯 값이 바뀌자마자 실행될 함수를 지정합니다. 이 함수 안에서 State를 조작할 수 있습니다.

[Code] 단위 변환기: kg ↔ lbs 

환자 체중 입력 시스템으로 구현

# 단위 변환 함수 (Callback)
def lbs_to_kg():
    # lbs 위젯 값을 가져와서 kg 위젯 값을 업데이트 (State 조작)
    st.session_state.kg = st.session_state.lbs / 2.2046

def kg_to_lbs():
    # kg 위젯 값을 가져와서 lbs 위젯 값을 업데이트
    st.session_state.lbs = st.session_state.kg * 2.2046

st.header("환자 체중 입력 (단위 자동 변환)")

col1, col2 = st.columns(2)

with col1:
    # key="kg" -> st.session_state["kg"]와 연결됨
    # on_change=kg_to_lbs -> 값이 바뀌면 kg_to_lbs 함수 실행
    st.number_input("킬로그램 (kg)", key="kg", on_change=kg_to_lbs)

with col2:
    st.number_input("파운드 (lbs)", key="lbs", on_change=lbs_to_kg)

st.write(f"DB 저장 값: {st.session_state.kg:.2f} kg")

 

5. [종합 실습] 가상 의료 데이터: "환자 대기열 시스템"

위에서 배운 Caching(데이터 로드), Session State(대기열 유지), Widget(입력)을 모두 합쳐서, 환자를 대기열에 추가하고 관리하는 시스템을 만들어봅시다.

import streamlit as st
import pandas as pd
import time

# --- 1. Caching: 초기 기본 데이터 로드 ---
@st.cache_data
def get_initial_patients():
    # 초기 대기 환자 리스트 (가상 데이터)
    return [
        {"name": "김철수", "symptom": "고혈압", "triage": "응급"},
        {"name": "이영희", "symptom": "당뇨", "triage": "일반"}
    ]

# --- 2. Session State: 대기열 관리 ---
# 앱이 켜지고 처음 한 번만 초기화
if "patient_queue" not in st.session_state:
    st.session_state["patient_queue"] = get_initial_patients()

# --- 3. Callback: 환자 추가 함수 ---
def add_patient():
    # 입력된 이름과 증상을 가져와서 리스트에 추가
    new_patient = {
        "name": st.session_state.input_name,
        "symptom": st.session_state.input_symptom,
        "triage": st.session_state.input_triage
    }
    st.session_state["patient_queue"].append(new_patient)
    # 입력창 초기화 (선택 사항)
    # st.session_state.input_name = "" 

# --- 4. UI 구성 ---
st.title("응급실 환자 대기열 관리 (Triage)")

# 입력 폼
with st.container():
    c1, c2, c3 = st.columns([2, 2, 1])
    with c1:
        st.text_input("환자 이름", key="input_name")
    with c2:
        st.text_input("주 호소 증상", key="input_symptom")
    with c3:
        st.selectbox("중증도", ["응급", "긴급", "일반"], key="input_triage")
    
    # 버튼 클릭 시 add_patient 콜백 실행
    st.button("환자 등록", on_click=add_patient)

st.markdown("---")

# 대기열 출력
st.subheader(f"현재 대기 환자: {len(st.session_state['patient_queue'])}명")

# 데이터프레임으로 변환하여 보여주기
if st.session_state["patient_queue"]:
    df_queue = pd.DataFrame(st.session_state["patient_queue"])
    st.dataframe(df_queue, use_container_width=True)
else:
    st.info("대기 중인 환자가 없습니다.")

 

요약

  1. Streamlit은 금붕어다: 상호작용할 때마다 처음부터 다시 실행됩니다(Rerun).
  2. @st.cache_data: 변하지 않는 데이터는 여기에 저장해 속도를 높이세요.
  3. st.session_state: 변하는 데이터(환자 목록, 카운터)는 여기에 저장해 기억력을 만들어주세요.
  4. key와 on_change: 위젯과 Session State를 연결하여 앱을 동적으로 만드세요.

 

'Study Note > Streamlit' 카테고리의 다른 글

[Streamlit 대시보드] 숫자로 말하는 데이터: st.metric과 멀티페이지 앱(MPA) 설계  (0) 2025.12.29
AI 모델, 직접 만들어서 웹으로 배포하기 (A to Z)  (1) 2025.12.29
속도는 올리고 코드는 깔끔하게  (0) 2025.12.29
의료 데이터 분석, 웹으로 띄우기 (기초편)  (0) 2025.12.29
머신러닝 모델 연동: "재입원 확률, AI에게 물어보세요"  (0) 2025.12.29
    'Study Note/Streamlit' 카테고리의 다른 글
    • [Streamlit 대시보드] 숫자로 말하는 데이터: st.metric과 멀티페이지 앱(MPA) 설계
    • AI 모델, 직접 만들어서 웹으로 배포하기 (A to Z)
    • 속도는 올리고 코드는 깔끔하게
    • 의료 데이터 분석, 웹으로 띄우기 (기초편)
    RyanNerd
    RyanNerd
    라이언 덕후의 일상 스토리~

    티스토리툴바