marimo batch API 实战指南:用 HTML 模板与mo.ui.batch批量组合交互控件
【免费下载链接】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
导读
mo.ui.batch是 marimo 提供的一种"组合式" UI 元素:它把一段带占位符的 HTML/Markdown 模板与若干个 UI 元素绑定在一起,模板负责排版与描述文字,UI 元素负责交互输入,二者合成为一个整体输出。本文基于 docs/api/inputs/batch.md 编写,结合源码实现与测试用例,讲解 batch 的两种创建方式、模板插值规则、value数据结构、元素访问 API、回调机制以及典型实战场景,帮助你用少量代码构建"日期范围选择""用户信息表单"这类自定义交互组件。
一、batch 是什么:模板与控件的"组合器"
在 marimo 中,每个mo.ui.*元素都同时具有两个面:展示面(一段 HTML,决定用户在界面上看到什么)和值面(一个 Python 值,决定后续单元格能拿到什么数据)。普通做法是一个单元格放一个控件,但当你需要多个控件以特定排版组合出现时,batch提供了一种更优雅的抽象。
batch的核心思想(见 源码 batch.py):
"Convert an HTML object with templated text into a UI element."
即:把一段带有{placeholder}占位符的 HTML/Markdown 文本,转换成一个 UI 元素。模板中的每个占位符都对应一个子 UI 元素,最终输出由模板排版 + 子控件渲染共同构成,而整个 batch 的value是一个dict(键为占位符名,值为对应子元素的值)。
从源码类继承关系看,batch继承自_batch_base,而_batch_base继承自UIElement[dict[str, JSONType], dict[str, object]]——前端的类型是{label => value update},Python 侧的类型是dict(映射 label 到 value)。前端通过marimo-dict标签渲染,对应 DictPlugin.tsx 插件。
batch 与 dictionary 的区别
源码中batch与ui.dictionary同源(都基于marimo-dict前端组件,且共用validate_and_clone校验逻辑),但定位不同:
ui.dictionary:用一个 Python dict 组织控件,仅用于布局/分组,没有模板文字;ui.batch:用 HTML/Markdown模板文字组织控件,适合"文字描述 + 控件"混合排版的场景(如"从 {start} 到 {end}")。
二、两种创建方式
方式一:Html.batch()链式调用(推荐)
任何Html对象(包括mo.md()和mo.html()的返回值)都带有一个batch方法,定义在 hypertext.py:
def batch(self, **elements: UIElement[Any, Any]) -> batch_plugin:示例(即文档中marimo-embed演示的核心逻辑):
import marimo as mo @app.cell def __(): el = mo.md("{start} → {end}").batch( start=mo.ui.date(label="Start Date"), end=mo.ui.date(label="End Date") ) el return @app.cell def __(): el.value return运行效果:单元格内渲染出mo.ui.date控件,模板文本{start} → {end}中的占位符被两个日期控件替换;在另一个单元格中读取el.value得到{"start": date1, "end": date2}。
方式二:直接实例化mo.ui.batch
当模板与元素需要分开定义、或元素需要动态构造时,可以直接实例化类(参考 batch.py 的__init__签名):
markdown = mo.md( ''' - What's your name?: {name} - When were you born?: {birthday} ''' ) user_info = mo.ui.batch( markdown, {"name": mo.ui.text(), "birthday": mo.ui.date()} ) user_info.value # {'name': '...', 'birthday': datetime.date(...)}两种方式完全等价——mo.md(...).batch(name=..., birthday=...)内部就是把self(Html 对象)和元素字典传给batch_plugin构造函数(hypertext.py)。
注意:直接实例化时,第二个参数必须是
Mapping[str, UIElement],且值必须全部是 UI 元素(详见下文"类型校验")。
三、模板插值规则与类型校验
占位符如何被替换
batch.__init__内部执行的关键一步(batch.py):
elements = validate_and_clone(elements) super().__init__( html=Html(self._html.text.format(**elements)), ... )即:先把元素字典做校验与克隆,然后调用 Python 字符串的str.format(**elements)将模板中的{name}、{birthday}替换为子元素的 HTML 渲染。由于每个UIElement都有_mime_/HTML 呈现能力,format之后得到的是一段完整的 HTML 片段——这就是为什么 batch 的"输出"天然是模板排版与控件混排的界面。
只接受 UIElement:ValueError 保护
validate_and_clone(batch.py)会逐个检查传入值是否为UIElement实例,否则抛出:
ValueError: `.batch` only accepts UIElements as arguments. Invalid keys: '...'测试 test_batch.py 明确覆盖了这些非法情形:
md("Example {thing}").batch(thing="thing") # 字符串 → 抛 ValueError md("Example {thing}").batch(thing=42) # 数字 → 抛 ValueError md("Example {thing}").batch(thing={"key": "value"}) # dict → 抛 ValueError元素会被克隆
validate_and_clone对每个合法元素调用_clone(),返回的是具有不同 ID 的副本(ui_element.py 中_clone的文档明确说明 "The clone will not synchronize with the original element")。测试同样验证了这一点:
cloned["a"]._id != a._id # 克隆体与原元素 ID 不同这意味着:你在模板中使用的元素与外界同名变量是两个独立实例,批内元素的变更不会反向污染原变量;同时批内子元素通过_register_as_view(parent=self, key=key)(batch.py)注册为父元素的"视图",从而与父元素建立联动关系。
四、读取与访问:value、elements 与索引
.value:批量值的字典
batch的value是一个只读dict,键为占位符名,值为对应子元素当前值。文档示例中单独一个单元格写el.value即可展示:
el.value # {'start': datetime.date(2026, 9, 1), 'end': datetime.date(2026, 9, 12)}实现上,_batch_base._convert_value(batch.py)在收到前端更新时会遍历value中的每个键,找到对应子元素,仅当值确实变化时才调用element._update(v)同步子元素状态,最终聚合出{key: element._value}字典——既保证数据一致,也避免无效更新触发多余重算(测试test_update_on_frontend_value_change_only验证了重复更新同一值只生效一次)。
.elements:子元素字典
通过.elements属性可以拿到批内全部子元素(克隆后的副本):
el.elements # {'start': <UIElement>, 'end': <UIElement>} el.elements["start"] # 直接操作 start 元素类字典访问协议
batch实现了完整的映射协议(batch.py),可以像 dict 一样使用:
len(el) # 2,元素个数 el["start"] # 按 key 取子元素 "start" in el # True,成员判断 list(el) # ['start', 'end'],迭代键 el.get("missing", None) # 带默认值的安全取值 el.items() # 键值对视图 el.values() # 子元素视图五、on_change 回调与嵌套场景
批级回调
batch支持on_change参数,签名与普通 UI 元素一致(batch.py):
def handle(values: dict) -> None: print("updated:", values) b = mo.md("{name}").batch( name=mo.ui.text(), on_change=handle, )回调接收的是整个 batch 的值字典dict[str, object],在批内任意子元素变化时触发。需要说明的是:示例源码中的batch构造函数目前暴露的是html, elements, on_change三个参数,label参数属于内部基类_batch_base的构造参数(默认""),对外通常无需传入。
嵌套场景中的回调保持
一个值得注意的实现细节:当 batch 作为子元素嵌套进ui.array等容器时,子元素的on_change仍然引用原始对象,而不是深拷贝后的对象。这是对 issue 的回归修复(test_batch.py):
models = [Model(i) for i in range(3)] view = ui.array([ ui.batch(Html("{box}"), elements={"box": ui.checkbox(on_change=m.set_state)}) for m in models ]) view._update({"0": {"box": True}}) assert models[0].state is True # 修改的是原始 Model因此,即便控件被 batch 包裹并克隆,on_change闭包中捕获的外部状态(如数据库连接、业务对象)依然指向正确的实例,适合在批量数据行编辑等场景中使用。
六、完整实战示例:日期范围选择器
结合 examples/ui/batch.py 中仓库自带的官方示例,一个典型的 batch 应用是"日期范围"选择:
import marimo as mo app = marimo.App(width="medium") @app.cell def _(): import marimo as mo return (mo,) @app.cell def _(mo): element = mo.md("{start} → {end}").batch( start=mo.ui.date(label="Start"), end=mo.ui.date(label="End"), ) element return (element,) @app.cell def _(element): # 在另一个单元格读取/使用批量值 start, end = element.value["start"], element.value["end"] mo.md(f"**报告区间**:{start} → {end},共 {(end - start).days} 天") return if __name__ == "__main__": app.run()运行方式
# 方式一:作为交互式 notebook 启动 marimo edit examples/ui/batch.py # 方式二:直接作为脚本运行 python examples/ui/batch.py更多组合思路
batch 的通用模式是"模板 + 任意 UI 元素",因此可以自由组合不同类型的控件:
# 混合控件:文本输入 + 滑块 + 下拉 report = mo.md( """ ### 报表配置 - 报表标题:{title} - 置信区间:{ci} - 图表类型:{chart} """ ).batch( title=mo.ui.text(placeholder="输入标题"), ci=mo.ui.slider(0, 100, value=95, label="置信度(%)"), chart=mo.ui.dropdown(["line", "bar", "scatter"], value="line"), ) report.value # {'title': '...', 'ci': 95, 'chart': 'line'}借助 batch,你可以把散落的多个控件封装为一个带语义标签的自定义组件,在后续单元格中通过report.value一次性获取全部输入,代码更紧凑、界面更整洁。
七、补充:API 速查
| 成员 | 类型/签名 | 说明 |
|---|---|---|
Html.batch(**elements) | 方法 | 链式创建 batch,mo.md/mo.html返回值均可调用 |
mo.ui.batch(html, elements) | 类构造 | html: Html,elements: Mapping[str, UIElement] |
on_change | Callable[[dict], None] \| None | 批值变化时的回调 |
.value | dict[str, Any] | 各子元素当前值(只读) |
.elements | dict[str, UIElement] | 批内子元素(克隆副本) |
el[key]/el.get(key) | 索引 | 按占位符名取子元素 |
len(el)/iter(el)/in | 协议 | 类 dict 访问 |
结语
mo.ui.batch是 marimo 中"以模板为骨架、以控件为血肉"的组合式交互方案:Html.batch()链式调用简洁直观,mo.ui.batch()直接实例化灵活可控;value提供统一的字典化取值,elements与类字典协议方便在代码中访问任意子元素,on_change回调(含嵌套场景)保证与业务状态正确联动。配合str.format插值实现,你可以在一个单元格内快速构建出贴合业务语义的自定义表单、范围选择器或配置面板。相关实现与测试可继续深入阅读 batch.py、hypertext.py、DictPlugin.tsx 与 test_batch.py。
【免费下载链接】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),仅供参考