오늘은 Streamlit으로 화면을 만드는 방법에서 한 단계 더 나아가, 사용자의 입력값과 화면 상태를 관리하는 방법을 배웠다.
Streamlit은 버튼을 누르거나 입력값을 변경할 때마다 파이썬 파일 전체를 다시 실행한다. 따라서 일반 변수만 사용하면 로그인 여부나 사용자가 선택한 값이 초기화될 수 있다. 이를 해결하기 위해 st.session_state, st.query_params, 브라우저 저장소와 캐시의 차이를 살펴봤다.
수업 후반에는 배운 내용을 활용해 로그인 → 설문 작성 → 결과 출력 순서로 동작하는 Streamlit 애플리케이션을 직접 구현했다.
Streamlit 애플리케이션의 전체 구조
오늘은 사용자의 요청이 처리되는 전체 흐름도 함께 정리했다.
사용자
→ Streamlit 화면
→ FastAPI 서버
→ LLM 또는 데이터베이스
→ 처리 결과 반환
Streamlit은 사용자가 직접 보는 화면을 담당하고, FastAPI는 화면에서 전달받은 요청을 처리하는 백엔드 역할을 한다.
예를 들어 사용자가 질문을 입력하면 Streamlit이 해당 내용을 FastAPI로 전달하고, FastAPI가 LLM이나 데이터베이스와 연결해 결과를 다시 화면에 보내주는 구조다.
이때 API 키처럼 외부에 공개되면 안 되는 정보는 Streamlit 화면에 직접 작성하기보다 백엔드에서 관리해야 한다는 점도 중요했다.
Streamlit이 코드를 다시 실행하는 방식
Streamlit의 가장 중요한 특징은 위젯을 조작할 때 코드가 위에서 아래로 다시 실행된다는 것이다.
예를 들어 아래처럼 일반 변수를 사용하면 버튼을 누른 뒤 코드가 재실행되면서 다시 False가 된다.
login = False
로그인에 성공한 뒤 값을 True로 변경하더라도 다음 실행에서 다시 False로 만들어질 수 있다.
이 문제를 해결하기 위해 st.session_state를 사용한다.
if "login" not in st.session_state:
st.session_state.login = False
st.session_state는 현재 Streamlit 세션에서 유지해야 하는 값을 저장하는 공간이다. 로그인 여부, 설문 완료 여부, 사용자가 입력한 값처럼 화면이 다시 실행되어도 기억해야 하는 정보를 보관할 수 있다.
st.session_state로 화면 상태 관리하기
오늘 실습에서는 다음과 같은 상태를 관리했다.
if "login" not in st.session_state:
st.session_state.login = False
if "survey_complete" not in st.session_state:
st.session_state.survey_complete = False
이 두 값을 이용하면 화면을 세 단계로 나눌 수 있다.
if st.session_state.login == False:
# 로그인 화면
elif st.session_state.survey_complete == False:
# 설문지 화면
else:
# 결과 화면
실행 흐름은 다음과 같다.
login = False
→ 로그인 화면
login = True
survey_complete = False
→ 설문지 화면
login = True
survey_complete = True
→ 결과 화면
처음에는 화면에 무엇을 배치할지만 생각했지만, 상태값을 기준으로 화면을 분리하면서 Streamlit 애플리케이션의 구조가 조금 더 명확하게 보이기 시작했다.
콜백 함수와 on_click
버튼을 눌렀을 때 특정 함수를 먼저 실행하도록 on_click을 사용할 수 있다는 것도 배웠다.
피자 선택 예제에서는 피자 종류, 도우, 치즈, 토핑 정보를 session_state에 저장하고 버튼을 누르면 선택값을 초기화했다.
def clear_state():
st.session_state.pizza = ""
st.session_state.dough = ""
st.session_state.cheeze = ""
st.session_state.topping = []
st.button(
"초기화",
on_click=clear_state
)
버튼을 클릭했을 때의 흐름은 다음과 같다.
버튼 클릭
→ 콜백 함수 실행
→ session_state 변경
→ 전체 코드 재실행
→ 변경된 상태로 화면 출력
콜백 함수는 화면 코드와 상태 변경 코드를 나누는 데 도움이 된다. 코드가 길어질수록 상태 초기화 함수, 버튼 처리 함수, 화면 출력 부분을 구분하는 것이 중요하다는 점을 알게 됐다.
st.query_params와 URL 상태 관리
st.query_params는 URL 뒤에 값을 저장하고 읽을 때 사용한다.
st.query_params["loginout"] = "login"
실행하면 URL에 다음과 비슷한 값이 추가된다.
?loginout=login
저장된 값은 다음처럼 읽을 수 있다.
login_state = st.query_params.get("loginout")
session_state와 달리 URL에 값이 들어 있기 때문에 새로고침 후에도 값을 확인할 수 있고, 같은 URL을 다른 사람에게 공유할 수도 있다.
두 기능의 차이는 다음처럼 정리할 수 있다.
기능저장 위치새로고침 후 유지
| st.session_state | Streamlit 세션 | 세션이 바뀌면 초기화될 수 있음 |
| st.query_params | URL | URL에 값이 남아 있으면 유지 |
다만 URL은 다른 사람에게 그대로 보일 수 있으므로 비밀번호, API 키, 개인정보를 저장하면 안 된다.
Session Storage와 Local Storage
브라우저가 제공하는 Session Storage와 Local Storage도 실습했다.
Session Storage
Session Storage는 현재 브라우저 탭이 열려 있는 동안 값을 보관한다.
새로고침
→ 값 유지
브라우저 탭 종료
→ 값 삭제
로그인 상태처럼 현재 사용 중인 탭에서만 유지할 정보에 사용할 수 있다.
Local Storage
Local Storage는 브라우저를 닫았다가 다시 실행해도 값이 남는다.
새로고침
→ 값 유지
브라우저 종료 후 재실행
→ 값 유지
자동 로그인 설정, 테마 선택, 사용자의 간단한 환경설정처럼 조금 더 오래 유지해야 하는 정보에 적합하다.
브라우저 저장소의 값은 개발자 도구에서 확인할 수 있었다.
F12
→ Application
→ Session Storage 또는 Local Storage
브라우저 저장소도 사용자가 직접 확인하거나 수정할 수 있으므로 중요한 보안 정보를 보관해서는 안 된다.
캐시와 상태 저장의 차이
캐시는 사용자의 상태를 저장하기 위한 기능이 아니라, 반복되는 계산이나 데이터 요청을 줄이기 위한 기능이다.
@st.cache_data
def load_data():
# 시간이 오래 걸리는 데이터 처리
return data
같은 조건으로 함수가 다시 호출되면 이전 결과를 재사용해 실행 속도를 높일 수 있다.
오늘 배운 저장 기능은 역할에 따라 구분할 필요가 있었다.
- st.session_state: 사용자의 현재 화면 상태 저장
- st.query_params: URL에 공유 가능한 상태 저장
- Session Storage: 현재 브라우저 탭에 값 저장
- Local Storage: 브라우저에 장기간 값 저장
- Cache: 반복 계산이나 데이터 요청 결과 재사용
처음에는 모두 값을 기억하는 기능처럼 보였지만, 실제로는 사용하는 목적과 유지되는 범위가 다르다는 것을 이해했다.
로그인 화면 구현
실습에서는 먼저 아이디와 비밀번호를 입력할 수 있는 로그인 화면을 만들었다.
input_id = st.text_input("아이디")
input_pwd = st.text_input("비밀번호", type="password")
login_button = st.button("로그인")
로그인 버튼을 누르면 입력값을 확인하도록 작성했다.
if login_button:
if input_id == "id01" and input_pwd == "pwd01":
st.session_state.login = True
st.rerun()
else:
st.error("아이디 또는 비밀번호가 올바르지 않습니다.")
st.error()는 화면에 빨간색 오류 안내 상자를 표시한다. 실제 파이썬 오류를 발생시키는 기능은 아니며, 사용자에게 잘못된 입력을 알려주는 역할을 한다.
st.rerun()은 현재 실행을 멈추고 코드를 위에서부터 다시 실행한다.
로그인 성공
→ login을 True로 변경
→ st.rerun()
→ 변경된 상태로 코드 재실행
→ 설문 화면 출력
실습 중에는 다음 코드를 작성해 로그인이 되지 않는 문제가 있었다.
st.session_state.login == True
==는 두 값이 같은지 비교하는 연산자이기 때문에 상태값이 변경되지 않았다. 아래처럼 대입 연산자 =를 사용해야 했다.
st.session_state.login = True
작은 기호 차이였지만 비교와 대입의 역할을 다시 확인할 수 있었다.
다양한 Streamlit 컴포넌트로 설문지 만들기
로그인에 성공하면 설문지 화면이 나타나도록 구현했다.
설문에는 수업에서 요구한 다양한 Streamlit 컴포넌트를 사용했다.
한 줄 문자 입력
survey_name = st.text_input("이름을 입력하세요.")
숫자 입력
survey_age = st.number_input(
"나이를 입력하세요.",
min_value=1,
max_value=100,
value=20
)
여러 줄 문자 입력
survey_intro = st.text_area(
"자기소개를 작성하세요."
)
하나의 항목 선택
survey_interest = st.selectbox(
"가장 관심 있는 분야를 선택하세요.",
["Python", "FastAPI", "Streamlit", "AI"]
)
여러 항목 선택
survey_skills = st.multiselect(
"배워본 기술을 모두 선택하세요.",
["Python", "Git", "FastAPI", "Streamlit", "Supabase"]
)
동의 여부 확인
survey_agree = st.checkbox(
"설문 내용 제출에 동의합니다."
)
범위에서 값 선택
survey_score = st.slider(
"오늘 수업의 이해도를 선택하세요.",
min_value=0,
max_value=10,
value=5
)
각 컴포넌트는 화면에 입력 도구를 표시할 뿐 아니라, 사용자가 입력하거나 선택한 값을 변수로 반환한다.
설문 결과를 Session State에 저장하기
설문 제출 버튼을 누르면 입력값을 session_state에 저장했다.
if survey_submit:
st.session_state.survey_name = survey_name
st.session_state.survey_age = survey_age
st.session_state.survey_intro = survey_intro
st.session_state.survey_interest = survey_interest
st.session_state.survey_skills = survey_skills
st.session_state.survey_agree = survey_agree
st.session_state.survey_score = survey_score
st.session_state.survey_complete = True
st.rerun()
제출이 완료되면 survey_complete를 True로 바꾸고 화면을 다시 실행한다.
그 결과 설문 화면은 사라지고 결과 화면이 나타난다.
st.write(f"이름: {st.session_state.survey_name}")
st.write(f"나이: {st.session_state.survey_age}")
st.write(f"자기소개: {st.session_state.survey_intro}")
st.write(f"관심 분야: {st.session_state.survey_interest}")
multiselect의 결과는 리스트이므로 join()을 이용해 보기 좋은 문자열로 바꿨다.
skills_text = ", ".join(st.session_state.survey_skills)
입력값 검증과 다시 작성 기능
이름을 입력하지 않았거나 설문 제출에 동의하지 않은 경우에는 바로 결과 화면으로 넘어가지 않도록 검사했다.
if survey_name == "":
st.warning("이름을 입력해 주세요.")
elif survey_agree == False:
st.warning("설문 내용 제출에 동의해 주세요.")
정상적으로 입력했을 때만 결과값을 저장하고 결과 화면으로 이동하도록 구성했다.
결과 화면에는 설문 다시 작성 버튼과 로그아웃 버튼도 추가했다.
if rewrite_button:
st.session_state.survey_complete = False
st.rerun()
if logout_button:
st.session_state.login = False
st.session_state.survey_complete = False
st.rerun()
최종적으로 다음과 같은 화면 흐름을 만들었다.
로그인 화면
→ 로그인 성공
→ 설문 작성
→ 설문 제출
→ 결과 확인
→ 설문 다시 작성 또는 로그아웃
실습하면서 해결한 문제
오늘 가장 먼저 막혔던 부분은 일반 변수와 session_state의 차이였다.
처음에는 다음처럼 작성했다.
login = False
survey_complete = False
하지만 Streamlit이 다시 실행될 때마다 값이 초기화될 수 있어 화면 전환 상태를 유지하기 어려웠다. 이를 st.session_state로 변경하면서 로그인 여부와 설문 완료 여부를 유지할 수 있었다.
또한 로그인 성공 코드를 작성할 때 =와 ==를 혼동했다.
st.session_state.login == True
이 코드는 값을 변경하지 않고 비교만 한다. 아래처럼 수정한 후 정상적으로 설문 화면으로 이동했다.
st.session_state.login = True
설문 결과를 출력할 때는 제출 전에 만들어진 지역 변수를 직접 사용하는 것이 아니라, session_state에 저장한 값을 읽어야 한다는 점도 배웠다. 화면이 다시 실행되면 이전 실행에서 만들어진 지역 변수는 그대로 유지되지 않기 때문이다.
오늘의 회고
오늘 수업을 통해 Streamlit은 단순히 화면에 버튼과 입력창을 배치하는 도구가 아니라, 상태를 어떻게 관리하느냐에 따라 애플리케이션의 흐름이 달라지는 프레임워크라는 점을 알게 됐다.
특히 session_state가 없다면 로그인, 설문 작성, 결과 출력처럼 여러 화면이 이어지는 기능을 구현하기 어렵다는 것을 직접 확인했다.
session_state, Query Params, Session Storage, Local Storage, Cache는 모두 값을 기억하는 기능처럼 보이지만 저장 위치와 목적이 다르다. 아직 각 기능을 실제 프로젝트에서 언제 선택해야 하는지는 더 연습이 필요하다.
앞으로는 로그인 상태뿐 아니라 채팅 기록, 사용자 설정, 페이지 이동 상태에도 session_state를 적용해 보고 싶다. 또한 지금 만든 설문 애플리케이션의 코드를 함수로 분리해 화면 코드와 상태 처리 코드를 더 깔끔하게 정리하는 연습도 필요하다.
#Streamlit #Streamlit실습 #SessionState #QueryParams #브라우저저장소 #Python #로그인기능 #설문조사앱 #상태관리 #AI국비지원
댓글