关于 marimo-notebooks
面向 marimo 笔记本开发者的 Python 使用指南,介绍响应式单元格、交互式界面和缓存等功能。内容还涵盖代码显示、变量管理及编辑前后的检查流程。
主要功能
- 01按依赖关系自动重跑单元格
- 02通过 Python 文件定义笔记本
- 03交互式输入与数据组件
- 04布局、状态和输出控制
- 05会话及持久化缓存
- 06笔记本检查与编辑规范
适用场景
- 01构建响应式数据分析笔记本
- 02制作带交互控件的教程
- 03探索并筛选表格数据
- 04缓存耗时计算结果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pymc-labs/ agent-skills --skill marimo-notebooks安装后在 Claude Code 中输入/marimo-notebooks确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CLAUDE.mdassets/data_analysis_template.pyminimal_template.pyreferences/advanced.mdcaching.mdui_components.mdwigglystuff.mdscripts/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。
最佳实践
- 将可复用代码封装在函数中 - 使中间变量保持局部作用域
- 使用有意义且唯一的变量名 - 例如
model1_sigma、model2_sigma - 不要跨单元格修改对象 - 在同一单元格中修改,或创建新变量
- 编写幂等单元格 - 相同输入产生相同输出
- 使用 mo.stop() - 根据条件或按钮控制耗时操作
- 使用延迟加载 - 对标签页或折叠面板中的耗时组件使用
mo.lazy() - 缓存耗时操作 - 会话级缓存使用
@mo.cache,磁盘缓存使用@mo.persistent_cache
重要:编辑前检查清单
在对 marimo 笔记本进行任何编辑之前:
- 读取当前文件状态 — 文件可能已被 marimo 编辑器修改
- 运行
marimo check notebook.py— 确认编辑前后文件均有效
完成编辑后:
- 搜索
print(— 将所有 print 语句替换为 marimo 输出(mo.md()、mo.stat()、mo.callout()) - 运行
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))
参考资料
- UI 组件:references/ui_components.md
- Wigglystuff 小组件:references/wigglystuff.md
- 缓存模式:references/caching.md
- 高级功能:references/advanced.md(SQL、部署、测试、路由)
- 官方文档:https://docs.marimo.io