uv 与 marimo 深度集成:用 uv 管理 Notebook 依赖、隔离环境与脚本化执行
marimo 是一个将交互式计算与传统软件的可复现性相结合的开源 Python notebook,它以纯 Python 脚本形式存储,因此可以像普通代码一样用 Git 版本化、以脚本形式运行、并以应用形式分享。本文基于 uv 官方集成指南,系统讲解在四种典型场景下(独立工具、内联脚本元数据、项目内、非项目环境)如何结合 uv 使用 marimo,并结合 uv 源码说明内联脚本元数据(PEP 723)的解析原理与 uv run 的执行逻辑,使读者既能直接复制命令,也能理解底层机制。
为什么 marimo 与 uv 天然契合
marimo 与传统 Jupyter 类 notebook 的关键区别在于:它的 notebook 就是普通的 .py 文件。这意味着:
- notebook 可以被 Git 跟踪和 diff;
- notebook 可以作为脚本直接执行(headless 运行);
- notebook 文件本身可以携带 PEP 723 内联脚本元数据,声明自己的 Python 版本与依赖——而这正是 uv 脚本管理的核心格式。
uv 对这一形态提供了完整支持:uvx 临时运行工具、uv add --script 维护脚本依赖、uv run 按声明自动构建环境并执行。下面的各节命令均可直接复制使用(来源:docs/guides/integration/marimo.md)。
场景一:将 marimo 作为独立工具使用
对于临时、即时的 notebook 访问,可以用 uvx 在隔离环境中随时启动 marimo 服务:
$ uvx marimo edit
指定编辑某个特定 notebook 文件:
$ uvx marimo edit my_notebook.py
uvx 是 uv tool run 的便捷别名:每次调用时 uv 会把 marimo 包安装到一个临时的、隔离的虚拟环境中并执行其提供的 marimo 可执行文件(见 docs/guides/tools.md)。其要点:
- 无需预先安装 marimo,也不污染全局 Python 环境;
- 环境是一次性的,适合 ad-hoc 使用;
- 如果当前目录是一个 uv 项目且你希望工具使用项目环境,官方建议改用
uv run而非uvx。
场景二:使用内联脚本元数据(PEP 723)管理 notebook 依赖
由于 marimo notebook 就是 Python 脚本,它们可以用 uv 支持的内联脚本元数据把依赖声明在文件头部,实现“自包含”的 notebook。
添加依赖
以给 notebook 增加 numpy 为例:
$ uv add --script my_notebook.py numpy
uv add --script 会在文件顶部写入/更新一个 script 元数据块(TOML 格式、每行以 # 前缀):
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "numpy",
# ]
# ///
交互式编辑带元数据的 notebook
$ uvx marimo edit --sandbox my_notebook.py
加上 --sandbox 后,marimo 会自动借助 uv 启动一个包含该脚本声明依赖的隔离虚拟环境来运行 notebook。此时若通过 marimo 的 UI 安装新包,安装包会被自动写回 notebook 的脚本元数据中——依赖始终与文件本身保持同步,可随 Git 一起版本化。
以脚本方式直接运行
不打开交互式会话,直接把 notebook 当脚本执行:
$ uv run my_notebook.py
源码层面:uv 如何解析内联元数据
uv 对内联脚本元数据的解析集中在 crates/uv-scripts/src/lib.rs 中,可以印证上述行为:
- 解析器以
# /// script作为起始标记,定位后逐行剥离前导#,得到纯 TOML 内容,并在最后一个恰好为# ///的行处闭合(ScriptTag::parse)。规范上要求:起始标记必须位于文件首行或紧跟换行符之后、标记块内每行都必须以#(加空格)开头,否则会被判定为“未闭合块”而报错——源码中的Pep723Error::UnclosedBlock、UnclosedBlockTrailingContent等错误类型(crates/uv-scripts/src/lib.rs)即对应这些校验,且单元测试覆盖了 shebang 共存、尾随内容、重复块等边界情况。 - 解析出的元数据结构
Pep723Metadata支持三个核心字段:dependencies(依赖列表)、requires-python(Python 版本要求)、以及tool表(crates/uv-scripts/src/lib.rs)。[tool.uv]子表进一步支持exclude-newer、自定义索引、sources等配置,可用于提升脚本的复现性。 - 元数据块必须提供
dependencies字段(即使为空),这是 uv 写入时的默认格式(见Pep723Script::create生成的dependencies = [])。 uv add --script更新依赖时,最终通过Pep723Script::write将新的元数据块原位替换写回文件,保留脚本其余内容(prelude/postlude)不变。
uv run 对 PEP 723 脚本的特殊处理
uv run 在执行命令时会首先判断目标是否为 PEP 723 脚本。从 crates/uv/src/commands/project/run.rs 可以看到,它区分 Pep723Item::Script(磁盘上的文件)、Pep723Item::Stdin(标准输入)和 Pep723Item::Remote(远程 URL)三种来源,并按脚本内声明的 requires-python 选择或下载匹配的 Python 解释器。
一个重要的行为细节:只要使用内联脚本元数据,即使 uv run 在某个 uv 项目中执行,项目的依赖也会被忽略,因此无需额外加 --no-project 标志(这一点在 docs/guides/scripts.md 中被明确标注为重要行为)。
场景三:在 uv 项目中使用 marimo
当 marimo notebook 是某个 uv 项目的一部分时(假设 marimo 已是项目依赖),可以直接用项目环境启动:
$ uv run marimo edit my_notebook.py
这样 notebook 运行时可 import 项目自身的模块。给 notebook 引入额外包有两条路:
- 用
uv add把包加入项目依赖(写入pyproject.toml并更新uv.lock); - 使用 marimo 内置的包安装 UI——它会替你调用
uv add,因此同样会持久化到项目声明中。
如果 marimo 本身不是项目依赖,仍可运行 notebook:
$ uv run --with marimo marimo edit my_notebook.py
--with 会在项目环境之上临时叠加 marimo 包(临时依赖机制见 docs/guides/scripts.md)。此方式下 notebook 依然能 import 项目模块,但有一个关键限制:在这种临时方式下通过 marimo UI 安装的包不会被写入项目依赖,且可能在后续启动中丢失。如果需要持久化,应改用 uv add。
场景四:在非项目环境中使用 marimo
对于没有 pyproject.toml 的普通虚拟环境,直接把 marimo 装进环境即可:
$ uv venv
$ uv pip install numpy
$ uv pip install marimo
$ uv run marimo edit
此时 notebook 内的 import numpy 可正常工作;通过 marimo UI 安装新包时,marimo 会代你调用 uv pip install 将包装入该环境。注意这与场景三(调用 uv add)不同:非项目环境下安装结果只反映在虚拟环境里,没有声明式文件记录,环境重建时依赖会丢失。
以脚本形式运行 notebook:跨场景统一入口
无论依赖如何管理——内联脚本元数据、项目依赖还是非项目环境——运行 notebook 的命令都是同一个:
$ uv run my_notebook.py
uv run 会自动完成环境准备(解析/同步依赖、选择或下载 Python 版本),然后以脚本方式执行 notebook,全程不打开浏览器交互式会话。这正是 marimo “notebook 即脚本” 设计在 CI、定时任务等无头场景下的价值所在。
四种场景速查
| 场景 | 启动命令 | 依赖持久化位置 | marimo UI 安装的行为 |
|---|---|---|---|
| 独立工具(ad-hoc) | uvx marimo edit |
无(临时环境) | 仅存在于临时环境 |
| 内联脚本元数据 | uvx marimo edit --sandbox my_notebook.py |
notebook 文件头部的 # /// script 块 |
自动写回脚本元数据 |
| uv 项目内 | uv run marimo edit my_notebook.py |
pyproject.toml + uv.lock |
代为调用 uv add |
| 项目内但 marimo 非依赖 | uv run --with marimo marimo edit my_notebook.py |
项目依赖不变,marimo 为临时包 | 不写入项目,可能丢失 |
| 非项目环境 | uv venv 后 uv pip install marimo,再 uv run marimo edit |
仅虚拟环境本身 | 代为调用 uv pip install |
| 脚本化执行(通用) | uv run my_notebook.py |
视所属场景 | 不适用(无 UI) |
延伸阅读
- 内联脚本元数据的完整机制(含
exclude-newer复现性配置、脚本锁定uv lock --script、shebang 用法):docs/guides/scripts.md; - uv 项目概念与结构:docs/concepts/projects/index.md;
uvx/ 工具管理:docs/guides/tools.md;- PEP 723 元数据解析实现:crates/uv-scripts/src/lib.rs;
uv run的执行流程:crates/uv/src/commands/project/run.rs。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00