7장. 웹 UI 연동과 배포
st.session_state 로 기억하기
식당에서 손님이 자리에 앉으면 직원이 테이블에 주문표를 하나 꽂아 둡니다. 주문서를 몇 번 새로 쓰든, 테이블의 주문표에 적힌 것은 그대로 남아 있습니다. 옆 테이블에는 옆 테이블의 주문표가 따로 있습니다. st.session_state 가 이 주문표입니다. 스크립트가 처음부터 다시 돌아도 지워지지 않고, 손님(브라우저 탭)마다 따로 있습니다.
왼쪽 파랑 상자는 보통 변수입니다. 다시 돌 때마다 count = 0 부터 시작합니다. 오른쪽 보라 상자는 st.session_state 입니다. 처음 열었을 때 한 번만 0 을 적어 두고, 그 뒤로는 적힌 값에 더해 갑니다. 여기서 테이블의 주문표가 st.session_state, 테이블마다 따로인 것이 손님(세션)마다 따로인 것입니다. 비유와 다른 점은, 주문표는 손님이 자리를 떠도 영수증으로 남지만 st.session_state 는 손님이 브라우저 탭을 닫거나 새로 고치면 사라진다는 것입니다. 오래 남겨야 하는 기록은 4장 실습 3처럼 파일이나 데이터베이스에 저장합니다.
session_state 다루기
| 하는 일 | 쓰는 모양 | 뜻 |
|---|---|---|
| 처음 한 번만 만들기 | if "messages" not in st.session_state: | 메모장에 이 이름이 없을 때(처음 열었을 때)만 |
| 값 적기 | st.session_state.messages = [] | 점(.)으로 적는다. st.session_state["messages"] 도 같다 |
| 값 읽기·고치기 | st.session_state.count += 1 | 보통 변수처럼 쓴다 |
| 이름이 있는지 | "count" in st.session_state | True / False |
| 지우기 | del st.session_state.count | 그 이름을 메모장에서 뺀다 |
짧은 예시
앞 절의 앱에서 보통 변수 대신 메모장에 숫자를 적습니다. 단추를 세 번 누르고, 같은 앱을 새로 열어 새 손님이 되어 봅니다.
from streamlit.testing.v1 import AppTest
APP = """
import streamlit as st
# 메모장에 count 가 없을 때(처음 열었을 때)만 0 을 적는다
if "count" not in st.session_state:
st.session_state.count = 0
if st.button("장바구니에 담기"):
st.session_state.count += 1
st.write(f"장바구니: {st.session_state.count}개")
"""
at = AppTest.from_string(APP).run()
for _ in range(3):
at.button[0].click().run()
print("세 번 누른 뒤:", at.markdown[0].value, "/ 메모장의 count:", at.session_state["count"])
# 같은 앱을 새로 열면 새 손님(새 세션)이다
other = AppTest.from_string(APP).run()
print("새 손님의 화면:", other.markdown[0].value)세 번 누른 뒤: 장바구니: 3개 / 메모장의 count: 3
새 손님의 화면: 장바구니: 0개세 번 누르자 3 이 되었습니다. 스크립트는 네 번 돌았지만 count = 0 은 처음 한 번만 실행되었기 때문입니다. at.session_state["count"] 로 앱 밖에서 메모장을 들여다볼 수도 있습니다. 새로 연 other 는 다른 손님이라 메모장이 비어 있고 0 개에서 시작합니다.
코드가 의미하는 것
이 코드는 테이블에 주문표를 꽂아 두고 장바구니 수를 적어 가는 일과, 옆 테이블에 새 손님을 앉히는 일을 흉내 낸 것입니다. 주문표 꽂기, 적어 가기, 새 손님 앉히기의 세 동작입니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
if "count" not in st.session_state: | 메모장에 이름이 없을 때만 안쪽을 돈다 | 손님이 처음 앉았을 때 주문표를 한 번만 꽂는 일입니다. 챗봇에서는 빈 대화 기록 목록을 여기서 만듭니다 |
st.session_state.count += 1 | 메모장의 값을 1 올린다 | 다시 실행을 건너 값이 쌓입니다. 대화 기록에 말을 덧붙이는 일도 같은 방식입니다 |
at.session_state["count"] | 앱 밖에서 메모장을 들여다본다 | 시험 코드가 화면 글뿐 아니라 기록 자체를 확인할 수 있습니다 |
other = AppTest.from_string(APP).run() | 같은 앱을 새로 연다 | 새 브라우저 탭으로 들어온 다른 손님입니다. 메모장이 따로 생깁니다 |
결과에서 읽을 것은 두 가지입니다.
- 세 번 누르자 3 이 되었습니다. 화면의 "장바구니: 3개" 와 메모장의
count3 이 같습니다. 0 을 적는 줄은 처음 한 번만 돌았습니다. - 새 손님은 0 개에서 시작합니다.
other의 화면은 "장바구니: 0개" 입니다. 웹 챗봇에서 손님마다 대화가 섞이지 않는 까닭이 이것입니다.
주의할 점
if "…" not in st.session_state:를 빼고st.session_state.count = 0만 적으면 다시 돌 때마다 0 으로 덮어써서 보통 변수와 같아집니다.- 4장에서는 손님마다 기록을 나누려고 세션 id 와
store사전을 직접 만들었습니다. Streamlit 에서는 브라우저 탭마다st.session_state가 따로 있으므로, 대화 기록을 여기에 두기만 해도 손님마다 나뉩니다. - 메모장은 서버의 메모리에 있습니다. 손님이 많고 대화가 길면 메모리를 많이 쓰므로, 4장의 Window 처럼 최근 기록만 남기는 것을 생각합니다.