폼(form)과 배치 입력
Ch 02에서 위젯을 조작하는 즉시 의존 셀이 재실행된다는 것을 배웠습니다. 대부분의 경우 이 즉각적인 반응이 유용합니다. 그런데 때로는 여러 입력을 한꺼번에 채운 다음, 사용자가 "제출" 버튼을 눌렀을 때만 실행이 일어나길 원하는 경우가 있습니다. 입력마다 즉시 재계산하면 너무 자주 실행되거나, 중간 상태에서 의도치 않은 결과가 나오기 때문입니다.
marimo의 폼(form)이 이 문제를 해결합니다.
폼의 동작 원리
폼은 위젯 묶음과 제출 버튼을 하나로 합친 구조입니다. 사용자가 값을 아무리 바꿔도 "제출"을 누르기 전까지는 다운스트림 셀이 재실행되지 않습니다. 제출 버튼을 누르는 순간 모든 입력 값이 한꺼번에 Python으로 전달됩니다.
mo.md().batch().form() 패턴
폼을 만드는 방법은 mo.md()에 플레이스홀더를 적고, .batch()로 위젯을 연결한 뒤, .form()으로 감싸는 것입니다.
import marimo as moform = mo.md(r"""### 모델 파라미터 설정- 학습률: {lr}- 에포크 수: {epochs}- 배치 크기: {batch_size}""").batch( lr=mo.ui.slider(0.0001, 0.1, value=0.01, step=0.0001, label="학습률"), epochs=mo.ui.number(1, 100, value=10, step=1, label="에포크"), batch_size=mo.ui.dropdown([16, 32, 64, 128], value=32, label="배치 크기"),).form()form
mo.md() 안의 {lr}, {epochs}, {batch_size}는 f-string의 표현식이 아닙니다. .batch()가 이 자리에 해당 위젯을 삽입합니다. 따라서 mo.md(r"""...""")처럼 raw string으로 작성해야 LaTeX나 백슬래시 처리에서 안전합니다.
폼이 제출되면 form.value에 딕셔너리가 들어옵니다.
# 제출 전: form.value는 None# 제출 후: form.value는 {"lr": 0.01, "epochs": 10, "batch_size": 32}if form.value is not None: lr = form.value["lr"] epochs = form.value["epochs"] batch_size = form.value["batch_size"] result = f"lr={lr}, epochs={epochs}, batch_size={batch_size} 로 학습을 시작합니다."else: result = "파라미터를 설정하고 제출하세요."result
폼이 아직 제출되지 않은 상태에서 form.value는 None입니다. 이 경우를 처리하지 않으면 TypeError가 발생하므로 항상 None 체크를 먼저 합니다.
간단한 폼 — 단일 위젯
폼 안에 꼭 여러 위젯이 있어야 하는 것은 아닙니다. 텍스트 입력 하나를 감싸 "검색 후 실행" 패턴으로 쓸 수도 있습니다.
import marimo as mosearch_form = mo.ui.text( label="검색어", placeholder="키워드 입력 후 Enter",).form()search_form
if search_form.value is not None: keyword = search_form.value result_text = f"'{keyword}' 검색 결과를 불러옵니다."else: result_text = "검색어를 입력하고 Enter를 누르세요."result_text
위젯 하나에 .form()을 직접 붙이면 해당 위젯 단독으로 폼이 됩니다. Enter 키나 제출 버튼을 누를 때까지 다운스트림이 재실행되지 않습니다.
폼과 즉각 반응형의 차이
두 패턴을 나란히 보면 차이가 명확합니다.
| 구분 | 동작 |
|---|---|
위젯 .value 직접 참조 |
위젯을 조작하는 즉시 의존 셀 재실행 |
.form() 사용 |
제출 버튼을 누를 때만 의존 셀 재실행 |
언제 어느 쪽을 쓸지는 상황에 따라 다릅니다.
- 슬라이더로 차트를 실시간으로 조정하는 경우처럼 즉각 피드백이 유용할 때는 위젯 직접 참조가 낫습니다.
- 모델 학습, API 호출처럼 한 번 실행하는 데 시간이 걸리는 작업은 폼으로 묶어 사용자가 명시적으로 제출할 때만 실행하는 것이 좋습니다.
- 여러 값을 채워야 의미 있는 입력 조합이 되는 경우(파라미터 셋 전체를 한꺼번에 제출)에도 폼이 적합합니다.
알고리즘 파라미터 폼 예시
연구 노트에서 자주 쓰는 형태의 폼을 하나 더 살펴봅니다. 수식이 포함된 폼입니다.
import marimo as moparam_form = mo.md(r"""### DP 알고리즘 파라미터- $\epsilon$ (탐험율): {epsilon}- $\gamma$ (할인율): {gamma}- 최대 반복 수: {max_iter}""").batch( epsilon=mo.ui.slider(0.01, 1.0, value=0.1, step=0.01), gamma=mo.ui.slider(0.5, 1.0, value=0.99, step=0.01), max_iter=mo.ui.number(10, 1000, value=100, step=10),).form(submit_button_label="실험 시작")param_form
.form(submit_button_label="실험 시작")처럼 제출 버튼 레이블을 바꿀 수 있습니다.
if param_form.value is not None: eps = param_form.value["epsilon"] gam = param_form.value["gamma"] itr = param_form.value["max_iter"] summary = f"epsilon={eps}, gamma={gam}, max_iter={itr} 설정으로 실험을 시작합니다."else: summary = "파라미터를 입력하고 '실험 시작'을 눌러주세요."summary
mo.stop으로 미제출 상태 처리하기
None 체크 대신 mo.stop을 사용하면 폼이 제출되지 않은 상태에서 아래 코드가 아예 실행되지 않도록 막을 수 있습니다.
import marimo as morun_form = mo.md(r"""분석 설정:- 데이터 파일: {filename}- 샘플 크기: {n_samples}""").batch( filename=mo.ui.text(label="파일 이름", placeholder="data.csv"), n_samples=mo.ui.number(10, 10000, value=100, step=10, label="샘플 크기"),).form()run_form
import marimo as momo.stop(run_form.value is None, mo.md("파일 이름과 샘플 크기를 입력한 뒤 제출하세요."))filename = run_form.value["filename"]n_samples = run_form.value["n_samples"]mo.md(f"**{filename}** 에서 {n_samples}개 샘플을 읽습니다.")
mo.stop(조건, 표시할 내용)은 조건이 True이면 해당 셀의 나머지 코드를 실행하지 않고 안내 메시지를 표시합니다. 폼 미제출 상태에서 아래 코드가 None에 접근해 에러를 내는 것을 깔끔하게 막아줍니다.
정리
.form()은 위젯 묶음을 제출 버튼과 묶어 사용자가 명시적으로 제출할 때만 의존 셀을 재실행합니다.- 폼을 만드는 기본 패턴은
mo.md(r"...{placeholder}...").batch(placeholder=위젯).form()입니다. form.value는 제출 전None, 제출 후{키: 값}딕셔너리입니다.- 위젯 하나에
.form()을 바로 붙여 단일 입력 폼으로도 사용할 수 있습니다. mo.stop()으로 폼 미제출 상태를 가드하면None관련 에러를 방지할 수 있습니다.