Streamlit을 쓰다 보면 당황스러운 순간이 옵니다. 버튼을 눌렀는데, 방금 입력한 환자 정보가 사라지거나 변수가 0으로 초기화되는 현상이죠. 이는 버그가 아니라 Streamlit의 고유한 작동 방식 때문입니다.
오늘은 이 '기억 상실' 문제를 해결하는 Session State와 Caching, 그리고 Widget과의 관계를 의료 시나리오로 풀어봅니다.
1. Streamlit의 작동 방식: "금붕어 기억력"
공식 문서에 따르면, Streamlit의 가장 큰 특징은 Data Flow입니다.
- 전체 재실행 (Rerun): 사용자가 버튼을 클릭하거나, 텍스트를 입력하는 등 상호작용(Interaction)이 발생할 때마다, Streamlit은 파이썬 스크립트 전체를 맨 위에서부터 맨 아래까지 다시 실행합니다.
- 초기화: 스크립트가 다시 실행되니, 코드 중간에 선언한 변수(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("대기 중인 환자가 없습니다.")
요약
- Streamlit은 금붕어다: 상호작용할 때마다 처음부터 다시 실행됩니다(Rerun).
- @st.cache_data: 변하지 않는 데이터는 여기에 저장해 속도를 높이세요.
- st.session_state: 변하는 데이터(환자 목록, 카운터)는 여기에 저장해 기억력을 만들어주세요.
- 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 |