mcpField
M
marimo-notebooks

marimo-notebooks

github.com/pymc-labs数据与数据库1940 次安装38 周前更新
代码仓库

关于 marimo-notebooks

面向 marimo 笔记本开发者的 Python 使用指南,介绍响应式单元格、交互式界面和缓存等功能。内容还涵盖代码显示、变量管理及编辑前后的检查流程。

主要功能

  1. 01按依赖关系自动重跑单元格
  2. 02通过 Python 文件定义笔记本
  3. 03交互式输入与数据组件
  4. 04布局、状态和输出控制
  5. 05会话及持久化缓存
  6. 06笔记本检查与编辑规范

适用场景

  1. 01构建响应式数据分析笔记本
  2. 02制作带交互控件的教程
  3. 03探索并筛选表格数据
  4. 04缓存耗时计算结果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pymc-labs/agent-skills --skill marimo-notebooks

安装后在 Claude Code 中输入/marimo-notebooks确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CLAUDE.md
assets/
data_analysis_template.py
minimal_template.py
references/
advanced.md
caching.md
ui_components.md
wigglystuff.md
scripts/
convert_notebook.py

详细介绍

Marimo 笔记本

Marimo 笔记本是以纯 .py 文件形式存储的响应式 Python 笔记本。单元格会在依赖项发生变化时自动执行,其执行方式基于有向无环图(DAG)。

核心概念

响应式模型
  • marimo 使用静态分析,根据变量引用和定义构建依赖图
  • 单元格运行时,所有引用其定义变量的单元格都会自动重新运行
  • 执行顺序遵循依赖图,而非单元格的视觉顺序
  • 每个全局变量必须且只能由一个单元格定义
  • marimo 不会跟踪对象的变更(例如 list.append())——在创建对象的同一单元格中修改它,或创建新变量
避免变量名冲突

每个全局变量必须且只能由一个单元格定义。有两种策略:

1. 将代码封装在函数中(适用于可复用模式):

@app.cell
def _(data):
    def compute_mean_with_new_col(df):
        temp = df.copy()
        temp["new_col"] = temp["x"] * 2
        return temp.mean()

    return (compute_mean_with_new_col(data),)

2. 使用有意义且唯一的变量名:

@app.cell
def _(model1_data):
    model1_transformed = model1_data.copy()
    model1_transformed["new_col"] = model1_transformed["x"] * 2
    return (model1_transformed,)

绝不要使用下划线前缀来生成唯一变量名。没有例外。

笔记本结构

import marimo

__generated_with = "0.10.0"
app = marimo.App(width="medium")

@app.cell
def _():
    import marimo as mo
    return (mo,)

@app.cell
def _(mo):
    mo.md("# Hello")
    return

if __name__ == "__main__":
    app.run()

关键规则:

  • 每个单元格都是使用 @app.cell 装饰的函数
  • 通过返回元组共享变量:return (var1, var2,)
  • 单元格通过参数接收变量:def _(mo, df):
  • 执行顺序遵循依赖图,而非位置
  • 为 CellTour 定位单元格时,使用描述性名称:def model_specification():

CLI 命令

# Create & Edit
marimo new                           # Create new notebook
marimo edit notebook.py              # Open editor
marimo edit notebook.py --watch      # Live reload on file changes

# Run as App
marimo run notebook.py               # Run as app (code hidden by default)
marimo run notebook.py --include-code  # Show code in app view

# Convert
marimo convert notebook.ipynb -o notebook.py  # Jupyter to marimo

# Export
marimo export html notebook.py -o out.html    # Static HTML
marimo export ipynb notebook.py -o out.ipynb  # To Jupyter

# Validate
marimo check notebook.py             # Lint and validate
marimo check notebook.py --fix       # Auto-fix issues

运行模式下的代码可见性

教程须知:默认情况下,marimo run 会隐藏代码。使用 mo.show_code() 显示代码。

mo.show_code() - 按单元格显示

重要提示:将 mo.show_code() 单独写在一行中作为语句调用,不要放在 return 语句中。

@app.cell
def model_definition(mo, pm, X, y):
    with pm.Model() as model:
        alpha = pm.Normal("alpha", mu=0, sigma=10)
        beta = pm.Normal("beta", mu=0, sigma=10)
        mu = alpha + beta * X
        pm.Normal("y", mu=mu, sigma=1, observed=y)

    # Show this cell's code alongside its output
    mo.show_code(model, position="above")
    return (model,)

错误与正确的写法:

# WRONG - do not put mo.show_code() in return statement
return mo.show_code(result)

# RIGHT - call as statement, then return separately  
mo.show_code(result, position="above")
return (result,)
  • position="above" 会先显示代码,再显示输出(最适合教程)
  • position="below" 会先显示输出,再显示代码(默认)

使用 mo.md() 编写 Markdown

@app.cell
def _(mo):
    mo.md(r"""
    # Title

    Interpolate Python: {slider}

    **LaTeX**: $f(x) = e^x$

    $$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$

    **Icons**: ::lucide:rocket:: or ::mdi:home::
    """)
    return
  • 对 LaTeX 使用原始字符串(r"""...""")
  • 插入 UI 元素:f"Value: {slider}"
  • 对于复杂对象:f"Plot: {mo.as_html(fig)}"

UI 组件(mo.ui.*)

基本输入
slider = mo.ui.slider(0, 100, value=50, label="Value")
number = mo.ui.number(0, 100, value=50)
text = mo.ui.text(value="", placeholder="Enter text")
checkbox = mo.ui.checkbox(value=False, label="Enable")
dropdown = mo.ui.dropdown(["a", "b", "c"], value="a")
radio = mo.ui.radio(["option1", "option2"], value="option1")
multiselect = mo.ui.multiselect(["a", "b", "c"])
按钮
button = mo.ui.button(label="Click")
run_button = mo.ui.run_button(label="Run")  # For triggering computation
数据组件
table = mo.ui.table(df)            # Interactive table with selection
dataframe = mo.ui.dataframe(df)    # Editable dataframe
data_explorer = mo.ui.data_explorer(df)  # No-code exploration
UI 元素分组
# Forms (require submit button)
form = mo.ui.text().form()

# Batch in markdown
form = mo.md("""
**Name**: {name}
**Age**: {age}
""").batch(
    name=mo.ui.text(),
    age=mo.ui.number(0, 120)
).form()

完整参考请参阅 references/ui_components.md。

布局函数

# Stacking
mo.hstack([el1, el2, el3], justify="center", gap=2)
mo.vstack([el1, el2, el3], align="start", gap=1)

# Containers
mo.accordion({"Section 1": content1, "Section 2": content2})
mo.tabs({"Tab 1": content1, "Tab 2": content2})
mo.callout(content, kind="info")  # info, warn, success, danger, neutral
mo.sidebar([nav_content])

# Display
mo.tree({"a": {"b": 1}})    # Tree view
mo.stat(value="42", label="Users", caption="+5%")
mo.lazy(expensive_component)  # Defer until visible

输出函数

mo.output.replace(new_content)  # Replace cell output
mo.output.append(additional)    # Append to output
mo.output.clear()               # Clear output

with mo.redirect_stdout():
    print("This goes to cell output")

状态指示器

# Progress bar
for item in mo.status.progress_bar(items, title="Processing"):
    process(item)

# Spinner
with mo.status.spinner(title="Loading..."):
    load_data()

控制流

# Stop execution conditionally
mo.stop(condition, mo.md("*Message when stopped*"))

# Example: require button click
run_button = mo.ui.run_button()
mo.stop(not run_button.value, mo.md("Click Run to execute"))
expensive_computation()

缓存

# In-memory cache (session only)
@mo.cache
def expensive_function(x, y):
    return compute(x, y)

# Persistent cache (survives restarts)
@mo.persistent_cache(name="embeddings")
def compute_embeddings(text):
    return model.encode(text)

有关模型输出缓存模式,请参阅 references/caching.md。

状态管理

警告:谨慎使用——超过 99% 的情况都不需要 mo.state()。

@app.cell
def _(mo):
    get_count, set_count = mo.state(0)
    return get_count, set_count

@app.cell
def _(mo, get_count, set_count):
    mo.ui.button(
        label=f"Count: {get_count()}",
        on_click=lambda _: set_count(lambda n: n + 1)
    )
    return

仅在需要维护历史记录、双向同步 UI 或引入循环时使用。

交互式绘图

# Altair with selection
chart = alt.Chart(df).mark_point().encode(x="x", y="y")
selection = mo.ui.altair_chart(chart, chart_selection="point")
selected_data = selection.value  # DataFrame of selected points

# Plotly
fig = px.scatter(df, x="x", y="y")
interactive = mo.ui.plotly(fig)
interactive.value  # Selected points

# Matplotlib interactive
fig, ax = plt.subplots()
ax.plot(x, y)
mo.mpl.interactive(fig)

支持:Matplotlib、Seaborn、Plotly、Altair、Bokeh、HoloViews、hvPlot

Wigglystuff 小组件

from wigglystuff import Slider2D, Paint, SortableList, Matrix, CellTour
import marimo as mo

slider2d = mo.ui.anywidget(Slider2D())
slider2d.x, slider2d.y

paint = mo.ui.anywidget(Paint(width=400, height=300))
paint.to_pil()

tour = mo.ui.anywidget(CellTour(
    steps=[
        {"cell_name": "intro", "title": "Welcome", "description": "..."},
        {"cell_name": "model", "title": "Model", "description": "..."},
    ],
    auto_start=False
))

所有小组件请参阅 references/wigglystuff.md。

最佳实践

  1. 将可复用代码封装在函数中 - 使中间变量保持局部作用域
  2. 使用有意义且唯一的变量名 - 例如 model1_sigma、model2_sigma
  3. 不要跨单元格修改对象 - 在同一单元格中修改,或创建新变量
  4. 编写幂等单元格 - 相同输入产生相同输出
  5. 使用 mo.stop() - 根据条件或按钮控制耗时操作
  6. 使用延迟加载 - 对标签页或折叠面板中的耗时组件使用 mo.lazy()
  7. 缓存耗时操作 - 会话级缓存使用 @mo.cache,磁盘缓存使用 @mo.persistent_cache

重要:编辑前检查清单

在对 marimo 笔记本进行任何编辑之前:

  1. 读取当前文件状态 — 文件可能已被 marimo 编辑器修改
  2. 运行 marimo check notebook.py — 确认编辑前后文件均有效

完成编辑后:

  1. 搜索 print( — 将所有 print 语句替换为 marimo 输出(mo.md()、mo.stat()、mo.callout())
  2. 运行 marimo check notebook.py — 确认没有引入错误

常见注意事项

输出与显示
  • 单元格中绝不能使用 print():打印语句在运行模式下不会显示。请始终使用:

    • mo.md(f"**Label:** {value}") — 格式化文本
    • mo.stat(value=f"{x}", label="Label") — 指标卡片
    • mo.callout(content, kind="info") — 提示框
    • 直接返回 dataframe/对象 — 自动显示
  • mo.show_code() 必须作为语句调用,不能放在 return 中

  • 移除输出时绝不能不加替代:删除打印语句时,需替换为等效的 marimo 输出

响应式与变量
  • 循环中的闭包:使用默认参数 lambda v, i=i: ...,而不是 lambda v: ... i
  • on_change 处理器:只有当元素绑定到全局变量时才有效
  • 动态 UI 元素:必须使用 mo.ui.array()、mo.ui.dictionary() 或 mo.ui.batch() 包装
  • 类型注解:除非加引号,否则会注册为引用:x: "SomeType"
库
  • matplotlib 图表被截断:输出前调用 plt.tight_layout()
  • dotenv:使用 dotenv.load_dotenv(dotenv.find_dotenv(usecwd=True))

参考资料

评论

支持 Markdown