
marimo 输出机制完全指南Cell Outputs、Console Outputs 与 mo.output API 实战【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo在 marimo 中单元格Cell既可以产生可视化输出Cell Outputs也可以产生写入stdout/stderr的控制台输出Console Outputs两者共同构成了 Notebook 界面与应用视图的呈现基础。本文基于仓库中的 docs/api/outputs.md 官方 API 文档结合marimo/_runtime/output/_output.py、marimo/_runtime/capture.py等源码实现与 tests/_runtime/output/test_output.py 测试用例系统讲解输出区的行为规则、mo.output系列 API 的底层原理、控制台输出的捕获与重定向以及mo.show_code()、mo.inspect()的应用场景。读完本文你将能精确控制每个单元格的最终呈现内容并为「以 App 形态分享 Notebook」做好输出编排。一、Cell Outputs单元格的可视化输出1.1 输出区的两种存在形态marimo 的每个单元格都可以拥有一个可视化输出Cell Outputs其呈现方式随运行形态不同而变化编辑模式下输出显示在单元格代码的上方代码相当于输出的图注。用户也可以在设置中将输出配置为显示在单元格下方。应用模式下如通过marimo run运行 Notebook 时应用界面本质上就是所有单元格输出的排列组合代码默认隐藏输出成为唯一可见的内容。单元格输出的默认来源是该单元格的最后一个表达式非None。例如在 examples/outputs/cell_output.py 中单元格以字符串字面量Hello, world!结尾该字符串即为单元格的可视化输出。1.2 编程式输出mo.output.replace()与mo.output.append()除了最后一个表达式这一隐式规则marimo 还提供了一组显式 API让你能够在单元格执行过程中任意时刻创建、替换、追加输出适合循环中逐步构建图表、多阶段渲染等场景。核心 API 定义于 marimo/_runtime/output/_output.pyAPI行为mo.output.replace(value)替换单元格现有输出若有为valuemo.output.append(value)在现有输出之后追加value多个输出按垂直堆叠排列mo.output.clear()清空单元格输出等价于replace(None)mo.output.replace_at_index(value, idx)替换输出列表中索引idx处的对象从源码看replace()的完整流程是先调用output.clear()清空该单元格的输出栈再对value执行formatting.as_html(value)格式化并追加到输出栈最后通过write_internal()将渲染结果广播给前端marimo/_runtime/output/_output.py而append()则不清空已有内容直接将新对象追加到输出栈尾部marimo/_runtime/output/_output.py。追加的多个输出最终会以垂直堆叠vstack的方式渲染——这正是CellOutputList.stack()的实现逻辑marimo/_runtime/cell_output_list.py。该容器还通过threading.RLock()保证线程安全因此多线程代码中并发调用输出 API 也是安全的。一个典型的应用是循环内增量构建输出import marimo as mo # 逐步追加最终垂直堆叠显示多个输出 for i in range(3): mo.output.append(mo.md(f**第 {i} 轮结果**)) # 之后仍可用 replace 覆盖为单一输出 mo.output.replace(mo.md(最终结论))1.3replace_at_index的边界语义mo.output.replace_at_index(value, idx)用于替换输出栈中指定位置的对象。需要注意其边界行为marimo/_runtime/cell_output_list.py若idx小于当前输出长度则替换该位置的对象若idx等于当前输出长度则等价于一次append若idx大于当前输出长度会抛出IndexError。这一语义在 tests/_runtime/output/test_output.py 的测试中被验证。1.4 关键警告最后一个表达式会替换已有输出原文档给出了一个极易踩坑的规则以非None表达式结尾的单元格等价于对该表达式调用mo.output.replace()——它会替换此前已经写入的所有输出。这意味着如果你在单元格中先mo.output.append()了若干内容最后一行却又是一个普通表达式如df.head()那么此前追加的内容会被最后这个表达式覆盖。若希望保留并追加而非替换请将最后一个表达式用mo.output.append(...)包裹import marimo as mo mo.output.append(mo.md(处理过程日志...)) mo.output.append(mo.md(统计摘要...)) # 错误示范最后的表达式会替换掉上面的两次 append # df.head() # 正确示范继续追加保留已有输出 mo.output.append(df.head())从实现层面看单元格执行框架会在表达式求值后将结果通过与replace()相同的路径写入输出区因此两者行为完全一致。1.5 输出格式化只发生一次在 tests/_runtime/output/test_output.py 中test_replace_formats_output_once验证了一个重要的实现细节mo.output.replace(value)对value的富展示格式化如调用_repr_html_只执行一次不会在渲染过程中重复触发格式化方法这既保证了性能也避免有状态对象的格式化被重复调用而产生副作用。二、在 App 视图中显示单元格代码mo.show_code()默认情况下以 App 形态运行 Notebook 时代码是隐藏的。若希望某个单元格的代码也出现在输出区、从而在 App 视图中可见可以使用mo.show_code()。其函数签名与参数如下marimo/_output/show_code.pymo.show_code(output: object None, *, position: Literal[above, below] below) - Htmloutput要与代码一同展示的输出对象省略时仅显示该单元格的代码本身position代码相对输出的位置取值为above代码在输出上方或below默认代码在输出下方。实现上show_code()会获取当前单元格的源代码并通过正则替换把代码中的mo.show_code(...)调用替换为...避免代码显示自身的递归循环见 marimo/_output/show_code.py然后借助只读的code_editor组件以代码高亮形式渲染并与输出通过vstack垂直排列。典型用法import marimo as mo def factorial(n: int) - int: if n 0: return 1 return n * factorial(n - 1) # 输出计算结果并在其下方显示本单元格代码 mo.show_code(factorial(5)) # 只显示代码、不显示任何输出 # mo.show_code()这一特性非常适合制作教学型、演示型 Notebook——读者在 App 视图中既能直接看到结果也能看到产生结果的代码。三、Console Outputsstdout/stderr输出及其在 App 中的呈现3.1 控制台输出的默认行为写入stdout/stderr的文本——包括print语句和日志——会出现在单元格下方的控制台输出区域与可视化输出区相互独立。例如print(这条文本出现在控制台输出区) 这是一个可视化输出需要特别注意的是默认情况下控制台输出在 App 视图中不显示。如果你希望print的输出出现在 App 界面中就必须使用 marimo 提供的工具函数将控制台输出捕获或重定向为单元格输出。3.2 捕获控制台输出mo.capture_stdout()/mo.capture_stderr()这两个上下文管理器将stdout/stderr写入捕获到内存缓冲区io.StringIO并返回该缓冲区供你读取marimo/_runtime/capture.pyimport marimo as mo with mo.capture_stdout() as buffer: print(Hello, world) mo.md(buffer.getvalue())上面的示例正是 examples/outputs/capture_console_outputs.py 中的真实用法先把print的输出捕获到buffer再用mo.md(buffer.getvalue())将其作为可视化输出渲染到输出区——这样就实现了让控制台文本出现在 App 中。对应的stderr捕获import marimo as mo import sys with mo.capture_stderr() as buffer: sys.stderr.write(Hello!) mo.md(buffer.getvalue())底层实现中marimo 的运行环境会将sys.stdout/sys.stderr替换为线程本地流代理ThreadLocalStreamProxy。capture_*会临时把当前线程的流切换为StringIO从而只捕获本线程的写入、不影响其他线程在非 marimo 环境代理不存在下则回退到标准库的contextlib.redirect_stdout/redirect_stderrmarimo/_runtime/capture.py。3.3 重定向控制台输出mo.redirect_stdout()/mo.redirect_stderr()与捕获后手动取出不同redirect_stdout/redirect_stderr将流写入直接转发到单元格的可视化输出区marimo/_runtime/capture.pyimport marimo as mo with mo.redirect_stdout(): print(Hello!) print(World!) # 上面的 print 输出会直接出现在该单元格的输出区这两个上下文管理器内部使用了一个_RedirectStream包装流其write()方法把收到的文本通过_output.append(plain_text(msg))逐条追加到单元格输出marimo/_runtime/capture.py。也就是说redirect_stdout是实时流式的——每条写入立即变成一条输出而capture_stdout则是整体捕获——所有文本汇总到缓冲区后由你自行决定如何展示。二者取舍如下函数行为适用场景mo.capture_stdout()/mo.capture_stderr()捕获到StringIO缓冲区返回给用户需要对文本做二次处理拼接、格式化、过滤后再展示mo.redirect_stdout()/mo.redirect_stderr()写入即时转发为单元格输出希望print的每一行直接出现在输出区无需手动搬运提示重定向与捕获都发生在 marimo 运行时的线程局部流之上多线程场景下只影响当前线程的流这一点与 tests/_runtime/test_capture.py 中test_capture_both等用例验证的捕获行为一致。3.4 清空控制台输出mo.output.clear_console()mo.output.clear_console()用于清空单元格下方的控制台输出区包括同一次运行中更早写入的文本来自print、日志或stdout/stderr。从源码看它执行两步操作marimo/_runtime/output/_output.py调用ctx.stream.flush_console()刷新控制台流广播一个console[]的空控制台通知通知前端清空展示区域。该行为在 tests/_runtime/output/test_output.py 的test_clear_console中有完整验证单元格先print(secret)再调用clear_console()测试断言前端收到了一条console []且status is None的清空通知。import marimo as mo print(这条日志会被清掉) mo.output.clear_console() print(这条日志保留)四、富对象检视mo.inspect()对于没有富展示形式_repr_html_等的 Python 对象marimo 提供了mo.inspect(obj)以丰富的 HTML 格式展示对象的属性、方法与文档便于在 Notebook 中交互式探索 API如argparse.ArgumentParser、SQLAlchemy 引擎等复杂对象。其实现位于 marimo/_plugins/stateless/inspect.py参数如下参数默认值作用obj必填要检视的对象helpFalse是否显示完整帮助文本否则只显示第一段methodsFalse是否显示方法docsTrue是否显示属性/方法的文档privateFalse是否显示私有属性以_开头dunderFalse是否显示双下划线特殊属性以__开头sortTrue是否按字母序排列属性allFalse一键开启methods、private、dundervalueTrue是否显示对象的值/repr用法示例import marimo as mo import argparse # 检视一个没有富展示的对象展示其方法 mo.inspect(argparse.ArgumentParser, methodsTrue) # 全量检视方法 私有属性 特殊属性 mo.inspect(obj, allTrue)inspect本质上是Html的子类会为不同类型class/function/method/module/instance/object渲染不同配色的类型徽标并按参数控制展示的属性范围让对象结构一目了然。五、总结输出控制的完整决策路径围绕输出区marimo 的 API 形成了一个完整的能力矩阵可按下述思路选择默认行为单元格最后一个表达式即为可视化输出编辑时显示在代码上方App 运行时即界面本身需要多段/动态输出用mo.output.append()垂直堆叠用mo.output.replace()覆盖旧输出用mo.output.replace_at_index()定点更新注意替换陷阱末尾的非None表达式会清空此前所有append内容务必用mo.output.append(...)包裹最后一个表达式需要在 App 中显示代码用mo.show_code(output, positionabove/below)需要把print/日志带进 App用mo.redirect_stdout()/redirect_stderr()实时转发或用mo.capture_stdout()/capture_stderr()捕获后自行加工用mo.output.clear_console()控制台清屏需要检视复杂对象用mo.inspect(obj, methodsTrue, ...)生成富 HTML 展示。所有 API 均已通过marimo顶层命名空间导出见 marimo/init.py可直接以mo.output.replace、mo.show_code、mo.capture_stdout等形式调用。理解可视化输出与控制台输出这两条独立通道以及replace/append的替换与堆叠语义是编写界面友好、可分享、可部署的 marimo Notebook 的关键。【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考