起因
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,运行时知道改 df → summary 重算 → 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。
我的工作流
数据探索:
marimo edit explore.py- 改 cell → 即时看下游结果(reactive)
- 完成的 cell 重构成 function
- 把 explore.py 里关键 function
import到 production code
production analysis dashboard:
marimo edit dashboard.py加 widgetmarimo 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 + 下游)。
踩过的坑
-
强制 DAG 无环:写
x = 1in cell 1 +x = 2in cell 2 报错
(same variable defined twice)。需要 refactor 到 function 或者
rename。开始不适应。 -
某些 imperative pattern 不支持:
for i in range(5): print(i)
in cell → marimo 不 reactive。包 function。 -
plot library 兼容:matplotlib OK;plotly / altair OK;
极少 niche lib 可能不渲染。 -
AI tab completion 弱于 Jupyter:jupyter-ai / VS Code Copilot
Jupyter 集成强。marimo 在追赶。 -
大 dataframe display 慢:reactive 每次跑都重 display。
df大时显式df.head(),或者 cache。
登录后参与评论。