news 2026/9/13 22:14:38

marimo 单元格输出机制全解析:从最后表达式到 mo.output 命令式输出

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
marimo 单元格输出机制全解析:从最后表达式到 mo.output 命令式输出

marimo 单元格输出机制全解析:从最后表达式到 mo.output 命令式输出

【免费下载链接】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)都可以拥有一个可视化的输出(output):编辑模式下它显示在单元格上方,代码则像"图注"一样衬在下文;把笔记本作为应用运行时,界面上展示的正是各个单元格输出的集合。本文围绕docs/examples/outputs/basic_output.md对应的示例 cell_output.py,结合marimo/_runtime/output/_output.pymarimo/_output/formatting.pymarimo/_output/show_code.py等源码,系统讲解单元格输出的默认规则、命令式输出 API、控制台输出分流以及与应用视图的关系,帮助你掌握在 marimo 中"掌控输出"的完整能力。

一、示例文档与示例代码:cell_output 的基本语义

关联文档 basic_output.md 是一个 marimo 嵌入式示例页面,通过marimo-embed-file指令直接嵌入 cell_output.py:

/// marimo-embed-file size: xxlarge mode: edit filepath: examples/outputs/cell_output.py ///

size: xxlarge指定嵌入区域为超大尺寸,mode: edit表示以编辑模式呈现(读者可以看到代码与输出并列),filepath指向仓库中真实的示例文件。也就是说,这个文档的"正文"本身就是一段可运行的 marimo 笔记本代码,其核心内容全部体现在示例文件中,这也是理解 marimo 输出语义最直接的入口。

示例代码 cell_output.py 由三个单元格构成(__generated_with = "0.19.7"标明生成版本,app = marimo.App()创建应用对象):

import marimo __generated_with = "0.19.7" app = marimo.App() @app.cell def _(mo): mo.md(""" The last expression of a cell is its visual output. This output appears above the cell when editing a notebook, with notebook code serving as a "caption" for the output. Outputs can be configured to appear below cells in the user settings. If running a notebook as an app, the output is the visual representation of the cell (code is hidden by default). """) return @app.cell def _(): "Hello, world!" return @app.cell def _(): import marimo as mo return (mo,) if __name__ == "__main__": app.run()

三个单元格分别演示了三层含义:

  1. mo.md生成富文本输出:第一个单元格的输出是一段 Markdown,它同时承担了"文档说明"的职责,展示了"代码即文档、文档即输出"的 marimo 风格;
  2. 字符串字面量即输出:第二个单元格只有一行"Hello, world!",没有任何print——在 marimo 中,单元格的最后一个表达式就是它的可视输出,因此字符串会被直接渲染出来;
  3. 模块导入与依赖:第三个单元格通过return (mo,)marimo模块暴露给其他单元格使用,这体现了 marimo 的依赖图机制——单元格之间通过显式的返回变量建立数据流。

二、核心规则:单元格的最后一个表达式就是它的输出

从源码角度印证"最后表达式即输出"这一规则。在运行时层面,marimo 的单元格执行器会捕获单元格体执行完毕后的最终值,作为RunResultoutput字段传递给后续的渲染流程(见 evaluator.py 中execute_cell_async的调用与RunResult(output=value, exception=None)的构造)。

这条规则带来的关键行为是:

  • print也能显示"Hello, world!"作为表达式求值后被渲染到输出区,这与 Jupyter 中"仅最后一个表达式自动显示"的语义一致;
  • 输出是"替换"而非"累积":单元格每次执行,输出区都会被最后一次执行的结果整体替换;
  • 返回None则不产生输出:例如示例中导入单元格返回的是(mo,)元组而非None,才会在界面中展示marimo模块的渲染结果。如果单元格最终表达式求值为None,输出区为空。

在编辑界面中,输出默认出现在单元格上方,代码作为输出的"说明文字"排布其下;用户也可以在设置中把输出改为显示在单元格下方。而当我们用marimo run把笔记本作为应用运行时,输出就是该单元格的可视化呈现——代码默认被隐藏

三、用mo.md构建富文本输出

单元格输出最常见的载体是mo.md()。它接受一个 Markdown 字符串并返回一个Html对象,该对象作为单元格的最后表达式时会被渲染成富文本。marimo 对 Markdown 做了扩展(详见 docs/guides/outputs.md 与marimo/_output/md.py):

  • 插值 Python 值:使用 f-string 可以把 Python 变量嵌入 Markdown,甚至直接嵌入 marimo 的 UI 元素,marimo 会自动识别并渲染它们;
  • LaTeX 支持:在 Markdown 编辑器中可启用r原始字符串模式书写 LaTeX 公式;
  • 扩展语法:支持/// details | 标题折叠块、/// attention | 标题等 admonition 提示框、:emoji:表情语法。

例如:

import marimo as mo name = mo.ui.text(placeholder="Your name here") mo.md( f""" Hi! What's your name? {name} """ )
mo.md(f"Hello, {name.value}!")

对于 matplotlib 等第三方绘图对象,可以直接用mo.as_html(figure)包装后嵌入 Markdown,从而接入 marimo 的媒体查看器:

mo.md( f""" Here's a plot! {mo.as_html(figure)} """ )

值得注意的细节:示例 cell_output.py 中mo.md("""...""")是单元格的最后一个表达式,因此它的渲染结果直接成为输出——这正是"Markdown 即输出"的典型用法。

四、命令式输出:mo.output.replace/append/clear/replace_at_index

虽然"最后表达式即输出"已能满足多数场景,但有时需要在单元格运行过程中增量构建输出。marimo 为此提供了命令式输出 API,实现在 marimo/_runtime/output/_output.py 中。

4.1mo.output.replace(value)

把单元格的整个输出区替换为value。源码逻辑是:获取当前执行上下文后,先output.clear(),再对value调用formatting.as_html(value)统一转成 HTML,随后output.append(html)并通过write_internal广播给前端。也就是说replace之后单元格输出区只有这一个对象

4.2mo.output.append(value)

value追加到输出区末尾,多个追加对象在界面上纵向堆叠。源码中每次append后都会把整个output.stack()重新广播到前端,保证界面与内存中的输出栈一致。

4.3mo.output.replace_at_index(value, idx)

按索引替换输出栈中的某个对象;当idx等于当前输出长度时,等价于一次append。这在需要更新输出列表中特定位置(例如更新图表、只刷新某一节文本)时非常有用。

4.4mo.output.clear()

清空单元格输出区。源码中它实际上是replace(None)的别名,即清空后不写入任何对象。

4.5 重要警告:最后一个表达式会替换已有输出

在 docs/api/outputs.md 中有一条醒目的警告:

以非None表达式结尾的单元格,等价于在该表达式上调用mo.output.replace()——它会替换你之前用命令式 API 写入的所有输出。如果希望保留已有输出并追加新内容,请用mo.output.append包裹最后一个表达式。

mo.output.append("first") mo.output.append("second") # 若这里直接写 "third",前面的 "first"/"second" 会被整体替换掉 # 正确做法: mo.output.append("third")

底层容器是marimo/_runtime/cell_output_list.py中的CellOutputList——一个线程安全的输出栈(内部持有RLock锁),appendclearreplace_at_index等操作都在锁保护下进行,因此跨线程增量更新输出也是安全的。

五、输出如何被渲染:格式化协议与媒体查看器

命令式 API 内部统一调用formatting.as_html(value),其核心是 marimo 的格式化协议,定义于 marimo/_output/formatting.py:

  • 每个格式化器是一个Callable[[T], tuple[KnownMimeType, str]],输入对象、输出(MIME 类型, 数据)二元组;
  • 注册优先级:先查顶层类型的注册格式化器,再沿类型的 MRO 继承链向上查找,找不到才退回到通用表示;
  • 用户自定义对象有两条接入路径:在类上实现_mime_方法返回(mime, data);或通过FormatterRegistry.add_formatter(type, func)注册一个格式化函数;
  • 对文本、JSON、DataFrame、matplotlib 图、音频、视频等常见类型,marimo 内置了丰富的格式化器(见marimo/_output/formatters/目录),输出时统一按 MIME 类型交给前端的媒体查看器渲染。

这意味着:任何 Python 对象只要满足格式化协议,就能作为单元格输出被优雅地展示——不必是字符串或 HTML。

六、控制台输出 vs 单元格输出

print、日志等写入stdout/stderr的内容属于控制台输出,默认显示在单元格下方的控制台区域,不会进入输出区,也不会出现在应用视图中。这一区分在 docs/api/outputs.md 的 "Console outputs" 一节中有明确说明。

若希望控制台输出并入输出区(从而在应用中可见),marimo 提供(详见 marimo/_runtime/output/_output.py 与marimo/_runtime/redirect_streams.py):

  • mo.redirect_stdout()/mo.redirect_stderr():上下文管理器,把print输出重定向到单元格输出区:
with mo.redirect_stdout(): print("Hello, world!")
  • mo.capture_stdout()/mo.capture_stderr():捕获但不重定向,适合把输出转成字符串再自行处理;
  • mo.output.clear_console():清空当前单元格的控制台输出区(包括本次运行中已写入的print/日志)。

此外,配置项中的std_stream_max_bytes会限制控制台输出的最大字节数(参见 config.py 中相关文档字符串),output_max_bytes则限制单元格输出的最大字节数——两者都关系到前端性能,超过限制的输出会被截断。

七、在应用视图中展示代码:mo.show_code

应用模式下代码默认隐藏。如果希望某个单元格的代码连同输出一起展示在应用视图中,可以使用mo.show_code(),实现在 marimo/_output/show_code.py:

def factorial(n: int) -> int: if n == 0: return 1 return n * factorial(n - 1) mo.show_code(factorial(5))
# 只展示代码,不展示输出 mo.show_code()

关键行为(源码可验证):

  • 参数position控制代码相对输出的位置:"above"(代码在上)或"below"(默认,代码在下),内部用vstack把只读code_editor与输出纵向堆叠;
  • 显示出的代码会通过substitute_show_code_with_arg把代码中所有mo.show_code(...)递归替换为...,避免死循环展示;
  • show_code()不带参数时只渲染一个只读代码编辑器,用于"代码即输出"的展示场景;
  • ContextNotInitializedError(非笔记本运行环境)下退化为直接返回as_html(output),保证脚本环境不报错。

八、小结:marimo 输出体系一览

能力API / 规则源码位置
默认输出单元格最后一个表达式executor/evaluator.py
富文本输出mo.md()+ Markdown 扩展marimo/_output/md.py
替换输出mo.output.replace(value)marimo/_runtime/output/_output.py
追加输出mo.output.append(value)同上
按索引替换mo.output.replace_at_index(value, idx)同上
清空输出mo.output.clear()(即replace(None)同上
线程安全输出栈CellOutputListmarimo/_runtime/cell_output_list.py
格式化协议_mime_方法 / 注册 formattermarimo/_output/formatting.py
控制台输出print→ 单元格下方控制台区marimo/_runtime/redirect_streams.py
输出+代码同显mo.show_code(output, position=...)marimo/_output/show_code.py

实践建议:默认场景下让单元格的最后一个表达式成为输出即可;需要"边运行边累积"时使用mo.output.append;需要整体替换时用mo.output.replace,但务必记得"最后一个非None表达式会替换已有输出"这一陷阱。理解输出与代码、控制台、应用视图三层关系,是写出界面友好、可复用、可直接发布为应用的 marimo 笔记本的基础。更多输出类型(DataFrame、图表、进度条、媒体等)可进一步阅读 guides/outputs.md 与 api/outputs.md。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 22:13:44

FOC驱动实战:电流环推导与无感控制调试全解析

说实话,FOC这东西我接触了不少项目,但要从一个“升魂浩荡”这种中二感拉满的项目名开始讲,我估计这个项目的命名人要么是重度网文读者,要么就是被电机参数折磨到精神恍惚之后起的代号。FOC,全称Field-Oriented Control…

作者头像 李华
网站建设 2026/9/13 22:10:56

UnoCSS MDC Extractor 指南:为 Markdown 组件语法提取原子类

UnoCSS MDC Extractor 指南:为 Markdown 组件语法提取原子类 【免费下载链接】unocss The instant on-demand atomic CSS engine. 项目地址: https://gitcode.com/GitHub_Trending/un/unocss UnoCSS 的 unocss/extractor-mdc 是一个专用于 MDC(Ma…

作者头像 李华