marimo vs Jupyter:reactive notebook 的演化

起因

Jupyter notebook 用了 10 年,痛点积累:

  • cell 乱序执行 → 隐藏状态 / 难复现
  • JSON 文件 git diff 不友好
  • 无 IDE 类型检查
  • 不能直接 run as script
  • import 难(要把 notebook 转 .py)

marimo(2023+)是新一代 Python notebook,reactive 设计:

  • 单 file = .py(git friendly + 可 import)
  • cell dependency 图 → 改一个 cell 自动重跑下游
  • 无隐藏状态(重启 → 一致结果)
  • 内置 UI widget(slider / dropdown 等)

pip install marimo
# 或者 uv add marimo
marimo new            # 新建 notebook
marimo edit my.py     # 编辑
marimo run my.py      # 当 app 跑(read-only)

文件格式

# my_notebook.py
import marimo

__generated_with = "0.6.0"
app = marimo.App()


@app.cell
def __():
    import polars as pl
    df = pl.read_csv('data.csv')
    df


@app.cell
def __(df):
    summary = df.group_by('country').agg(pl.col('amount').sum())
    summary


@app.cell
def __(summary):
    summary.plot.bar(x='country', y='amount')

每 cell 是函数:参数 = 它依赖的变量。
marimo 自动构建 DAG,运行时知道改 dfsummary 重算 → plot 重算。

reactivity

改第一个 cell,比如 pl.read_csv('other.csv')

  • marimo 自动识别 df 变了
  • 下游 cell(用 df 的)自动跑
  • 不用手动 Shift+Enter 每个 cell

vs Jupyter:你必须记得"我改了 df,要 re-run 下游"。少 re-run 一个就
state 不一致。

无隐藏状态

Jupyter 经典 bug:

# cell 1
x = 5

# cell 2
print(x)     # 5

# cell 1 修改
x = 10
# 不重跑 cell 2

# cell 3
print(x)     # 10
# cell 2 显示 5,cell 3 显示 10 → 矛盾状态

marimo 不允许:cell 1 改了 → cell 2 / 3 都重跑。
restart kernel + run all 跟正常 run 一样。

git friendly

$ git diff my_notebook.py
- df = pl.read_csv('data.csv')
+ df = pl.read_csv('orders.csv')

正常 Python diff。

vs Jupyter .ipynb

$ git diff my.ipynb
- "execution_count": 12,
+ "execution_count": 13,
- "metadata": {"id": "abc"},
+ "metadata": {"id": "def"},
- ... 几百行 base64 image diff

.ipynb 是 JSON + base64 编码的 output,PR review 痛苦。
工具如 nbdime / jupytext 能缓解但 marimo 原生 .py 更纯。

当 script 跑

python my_notebook.py
# 跟 jupyter nbconvert 不一样,marimo .py 真的是 Python 文件,直接跑

CI 跑 notebook 测试 → 一行命令。

当 import 用

# main.py
from my_notebook import summary       # marimo 让 cell 变量 import 出来

marimo notebook = Python module。
prototype 完直接被生产 code 引用,不用"先把 cell 改 function 再 import"。

UI widget

@app.cell
def __(mo):
    slider = mo.ui.slider(0, 100, value=50, label='Sample size')
    slider

@app.cell
def __(slider, df):
    sample = df.head(slider.value)
    sample

slider 调 → 自动重算 sample → 显新结果。
不用 ipywidgets 复杂 callback。

marimo as app

marimo run my.py --host 0.0.0.0 --port 8000

变成 web app(read-only),URL 分享给同事。
UI widget 仍可交互。

替代 streamlit 简单场景。

vs Jupyter 优势汇总

Jupyter marimo
文件格式 JSON .ipynb Python .py
隐藏状态
Reactive
Git diff
Run as script 要 nbconvert 直接
Import 要 nbconvert 直接
UI widget ipywidgets marimo.ui
AI 友好度 弱(JSON) 强(.py)

Jupyter 仍胜的场景

  • 巨大社区(教程 / 大量 .ipynb 内容)
  • Google Colab / Kaggle / VS Code 内置 Jupyter
  • 自由 cell 顺序(marimo 强制 DAG 无环)
  • 显示 rich output 历史悠久 + 稳

新项目我用 marimo,老 .ipynb 继续 Jupyter。

我的工作流

数据探索:

  1. marimo edit explore.py
  2. 改 cell → 即时看下游结果(reactive)
  3. 完成的 cell 重构成 function
  4. 把 explore.py 里关键 function import 到 production code

production analysis dashboard:

  1. marimo edit dashboard.py 加 widget
  2. marimo run dashboard.py --headless 部署

替代 Jupyter + nbconvert + streamlit 三个工具。

与 nbdev 对比

nbdev(fast.ai):Jupyter 当源码,notebook 直接 ship 为库。
marimo:notebook 就是 .py,本来就是源码。

nbdev 改进 Jupyter;marimo 重新设计。我选 marimo(少抽象层)。

与 Pluto.jl 对比

Julia 的 Pluto 是 marimo 的灵感来源(reactive notebook)。
marimo 在 Python 把这模型实现。

性能

reactive DAG 计算开销小(几 ms / 改动)。
大 cell 仍跟 Jupyter 一样跑。
增量执行有时还更快(只跑变化的 cell + 下游)。

踩过的坑

  1. 强制 DAG 无环:写 x = 1 in cell 1 + x = 2 in cell 2 报错
    (same variable defined twice)。需要 refactor 到 function 或者
    rename。开始不适应。

  2. 某些 imperative pattern 不支持for i in range(5): print(i)
    in cell → marimo 不 reactive。包 function。

  3. plot library 兼容:matplotlib OK;plotly / altair OK;
    极少 niche lib 可能不渲染。

  4. AI tab completion 弱于 Jupyter:jupyter-ai / VS Code Copilot
    Jupyter 集成强。marimo 在追赶。

  5. 大 dataframe display 慢:reactive 每次跑都重 display。
    df 大时显式 df.head(),或者 cache。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。