iBetter Books
수정

위젯과 반응형 상태 연결하기

Ch 01에서 위젯을 만들고 .value로 값을 읽는 방법을 봤습니다. 이 챕터에서는 그 값이 변할 때 다른 셀이 자동으로 반응하는 구조를 이해합니다. PART 02에서 다룬 DAG 원리의 직접적인 연장입니다.

위젯도 DAG 노드다

PART 02 Ch 02에서 배운 내용을 떠올려봅니다. raw_data를 정의하는 셀이 변경되면, raw_data를 참조하는 셀이 자동으로 재실행됩니다. 위젯도 정확히 같은 방식으로 동작합니다.

슬라이더가 하나 있고, 그 슬라이더의 값을 이용해 제곱수 목록을 만드는 예시입니다.

import marimo as mo# 셀 A — 위젯 정의 (DAG 노드)n = mo.ui.slider(1, 20, value=5, label="n")n
# 셀 B — n.value에 의존 (n이 변하면 이 셀이 재실행됨)squares = [i ** 2 for i in range(n.value)]squares

셀 A가 n이라는 변수를 정의합니다. 셀 B가 n.value를 참조하므로 n에 의존합니다. 슬라이더를 움직이면 n.value가 바뀌고, DAG는 셀 B를 재실행해야 한다는 것을 압니다. 사람이 따로 실행 버튼을 누를 필요가 없습니다.

의존성 흐름을 그림으로 나타내면 이렇습니다.

%% 위젯 의존성 흐름 (DAG) graph TD A["셀 A (n 정의, 슬라이더 렌더링)"] B["셀 B (squares 계산)"] A -->|n.value 참조| B

전역 변수 할당이 필수인 이유

위젯의 반응형 동작이 성립하려면 위젯을 전역 변수에 할당해야 합니다. 셀 안에서 _로 시작하는 이름을 쓰면 셀-로컬 변수가 되어 다른 셀에서 참조할 수 없습니다.

import marimo as mo# 이렇게 쓰면 다른 셀에서 참조 불가 (셀-로컬 변수)_s = mo.ui.slider(0, 10)_s
import marimo as mo# 이렇게 써야 다른 셀에서 참조 가능 (전역 변수)s = mo.ui.slider(0, 10)s

이름이 _로 시작하지 않는 한, 셀에서 정의된 모든 변수는 전역 네임스페이스에 놓입니다. 위젯도 마찬가지입니다.

드롭다운으로 데이터 필터링하기

실전에서 자주 쓰이는 패턴입니다. 드롭다운 값을 바꾸면 필터링 결과가 즉시 갱신됩니다.

import marimo as mo# 셀 1 — 카테고리 선택 드롭다운category = mo.ui.dropdown(    ["전체", "과학", "수학", "역사"],    value="전체",    label="카테고리 선택",)category
# 셀 2 — category.value에 따라 데이터 필터링books = [    {"title": "코스모스", "cat": "과학"},    {"title": "수학의 정석", "cat": "수학"},    {"title": "총균쇠", "cat": "역사"},    {"title": "파인만 강의", "cat": "과학"},]if category.value == "전체":    filtered = bookselse:    filtered = [b for b in books if b["cat"] == category.value]filtered

드롭다운에서 "과학"을 고르면 셀 2가 재실행되어 filtered가 과학 분야 책만 담은 리스트로 바뀝니다. 이것이 marimo 인터랙티브 필터의 기본 구조입니다.

여러 위젯을 동시에 참조하기

하나의 셀이 여러 위젯 값에 의존해도 됩니다. 어느 위젯이 바뀌더라도 해당 셀이 재실행됩니다.

import marimo as momin_score = mo.ui.slider(0, 100, value=60, label="최소 점수")min_score
import marimo as mosubject = mo.ui.dropdown(["수학", "영어", "과학"], value="수학", label="과목")subject
# min_score와 subject 모두에 의존# 어느 위젯이 바뀌어도 이 셀이 재실행됨students = [    {"name": "Alice", "수학": 85, "영어": 70, "과학": 90},    {"name": "Bob", "수학": 55, "영어": 80, "과학": 60},    {"name": "Carol", "수학": 75, "영어": 65, "과학": 88},]result = [    s["name"]    for s in students    if s[subject.value] >= min_score.value]result

슬라이더를 올리거나 과목을 바꾸면 result가 즉시 갱신됩니다.

mo.state — 공유 상태 만들기

위젯 두 개가 서로 같은 값을 공유해야 하는 경우가 있습니다. 예를 들어 버튼을 누를 때마다 카운터가 오르내리는 구조입니다. 이때는 mo.state()를 사용합니다.

mo.state(초기값)(getter, setter) 쌍을 반환합니다. getter를 호출하면 현재 상태 값을 읽고, setter를 호출하면 상태를 갱신합니다. 상태가 바뀌면 getter를 참조하는 셀이 재실행됩니다.

import marimo as mo# 셀 1 — 공유 상태 정의 (초기값 0)get_counter, set_counter = mo.state(0)
import marimo as mo# 셀 2 — 증가·감소 버튼# on_change 콜백에서 set_counter로 상태를 갱신합니다.increment = mo.ui.button(    label="증가",    on_change=lambda _: set_counter(lambda v: v + 1),)decrement = mo.ui.button(    label="감소",    on_change=lambda _: set_counter(lambda v: v - 1),)mo.hstack([increment, decrement])
# 셀 3 — get_counter()를 참조해 카운터 값을 표시# get_counter()가 바뀌면 이 셀이 재실행됩니다.mo.md(f"카운터: **{get_counter()}**")

셀 2의 버튼을 클릭하면 on_change 콜백이 실행되어 set_counter로 상태를 변경합니다. 상태 변경은 get_counter()를 참조하는 셀 3을 재실행합니다. 카운터 숫자가 즉시 갱신됩니다.

레이아웃 API(mo.hstack, mo.vstack)의 자세한 사용법은 PART 07에서 다룹니다. 여기서는 두 버튼을 가로로 나란히 배치하기 위해 mo.hstack을 한 번 사용했습니다.

슬라이더와 상태를 연결하기

슬라이더에 초기값을 상태로 고정하고 싶을 때는 on_change에 setter를 직접 전달합니다.

import marimo as mo# 셀 1 — 상태와 슬라이더 동시 정의get_x, set_x = mo.state(5)x_slider = mo.ui.slider(0, 10, value=get_x(), on_change=set_x, label="x")x_slider

슬라이더를 움직이면 on_changeset_x를 호출해 상태를 갱신합니다. 다른 방법으로도 set_x를 호출해 슬라이더 값을 초기화할 수 있습니다. 슬라이더를 재생성하지 않고 값만 동기화하는 패턴입니다.

언제 .value 직접 참조를, 언제 mo.state를 쓸까

두 패턴의 사용 기준을 간단히 정리합니다.

상황 권장 방식
위젯 값을 다른 셀에서 읽기만 할 때 .value 직접 참조
여러 위젯이 같은 값을 공유해야 할 때 mo.state
버튼 클릭으로 카운터나 상태를 누적 변경할 때 mo.state + on_change
위젯 값을 외부에서 초기화·리셋해야 할 때 mo.state + on_change

대부분의 경우는 .value 직접 참조로 충분합니다. mo.state는 단방향 의존만으로 해결하기 어려운 복잡한 상호작용에 씁니다.

정리

  • 위젯도 DAG의 노드입니다. .value를 참조하는 셀은 위젯 조작 시 자동으로 재실행됩니다.
  • 반응형 연결이 성립하려면 위젯을 전역 변수(_ 접두사 없는 이름)에 할당해야 합니다.
  • 여러 위젯 값을 한 셀에서 참조하면, 어느 위젯이 바뀌어도 그 셀이 재실행됩니다.
  • mo.state()는 여러 위젯이 같은 값을 공유하거나 버튼 클릭으로 상태를 누적 변경할 때 사용합니다.