- 开发工具
【免费下载链接】jupytext
Jupyter Notebooks as Markdown Documents, Julia, Python or R scripts
本篇技术指南以 Jupytext 仓库中的ir_notebook.md示例为骨架,系统讲解MyST(Markedly Structured Text)Markdown 格式如何承载 Jupyter Notebook:从 YAML 前导元数据、{code-cell}指令到源码级转换原理与 CLI/API 操作方式。读完你将掌握:如何把一个 R 内核的.ipynb转为 MyST 文档、如何在 Jupyter 中直接打开与配对使用该格式、以及转换背后的底层实现机制。
一、示例文件与它在仓库中的位置
本文聚焦的关联文档是仓库中的测试样本 ir_notebook.md,它是由同目录下的原始 Notebook ir_notebook.ipynb 转换而来的MyST Markdown 版本。原始 Notebook 使用 R 的 IR 内核(kernel 名ir),包含 4 个单元格:
| 单元格 | 类型 | 内容 |
|---|---|---|
| 1 | markdown | This is a jupyter notebook that uses the IR kernel. |
| 2 | code | sum(1:10)(输出[1] 55) |
| 3 | code | plot(cars)(输出 PNG 图像) |
| 4 | code | 空单元格 |
该文件与ipynb_to_md、ipynb_to_pandoc目录下的同名文件一起,构成了 Jupytext 多格式输出的对比样本,非常适合用来理解不同 Markdown 变体之间表示同一个 Notebook 的差异。相关格式的官方说明可参考仓库文档 Notebooks as Markdown。
二、MyST 格式概览:Notebook 的文本化表示
MyST 是 CommonMark 的一种受控扩展,把 reStructuredText 中最有价值的 Sphinx 指令与角色带进了 Markdown。MyST-NB 与 Jupyter Book 等工具在 MyST 之上实现了 Jupyter Notebook 到 Sphinx 文档的直接转换。
Jupytext 对 MyST 的定位与其他文本格式一致:用纯文本文件承载 Notebook 的全部结构与内容,使其可以被 git 追踪、diff 审查、IDE 编辑,同时又能无损还原回.ipynb。MyST 与 Jupytext 自家 Markdown 格式的关键区别在于单元格元数据的编码方式——MyST 使用 YAML 块(支持---围栏块与:key: value紧凑行两种形态),而 Jupytext Markdown 使用key=value内联语法。
下面的内容完全来自转换产物 ir_notebook.md(保留原文,仅作排版展示):
--- kernelspec: display_name: R language: R name: ir --- This is a jupyter notebook that uses the IR kernel. ```{code-cell} r sum(1:10) ``` ```{code-cell} r plot(cars) ``` ```{code-cell} r ```短短 19 行文本,完整保留了:内核信息(YAML 前导块)、一个 Markdown 单元格、三个代码单元格(含一个空单元格)。这就是 MyST Notebook 的典型形态。
三、YAML 前导元数据:内核信息如何被保留与还原
文件开头是标准的 YAML frontmatter,用---围栏包裹:
--- kernelspec: display_name: R language: R name: ir ---它对应原始 Notebook 中metadata.kernelspec字段(display_name: R、language: R、name: ir),作用是在 Jupyter 中打开该文本文件时告诉服务器应该使用哪个内核。值得注意的是,转换时 Jupytext 只保留了kernelspec,而原始 Notebook 中language_info里的codemirror_mode、mimetype、pygments_lexer、version等字段被过滤掉了——这正是 Jupytext 元数据过滤机制的体现(见 metadata_filter.py),文本文件只保留运行时真正需要的信息。
从源码看,Jupytext 对 MyST 文件的识别强依赖这个前导块。myst.py 中的matches_mystnb()函数按以下顺序判定一个文件是否为 MyST Notebook:
- 扩展名为
.myst、.mystnb、.mnb时直接判定为 MyST(myst_extensions() 还额外允许.md); - 若要求元数据(
requires_meta=True默认开启),文本必须以---开头,否则直接返回False; - 解析 frontmatter,检查其中
jupytext.text_representation.format_name是否为myst; - 全文是否存在以
{code-cell}或{raw-cell}开头的围栏代码块。
这意味着:一个 MyST Notebook 几乎总是以---开头的 YAML 元数据块起始,这也是阅读和手写该格式时最重要的结构性约定。
四、代码单元格:{code-cell}指令与语言标注
MyST 格式中,代码单元格使用围栏代码块加指令语法,{code-cell}是 Jupytext 在 myst.py 中定义的代码单元格指令(RAW_DIRECTIVE = "{raw-cell}"对应原始单元格):
```{code-cell} r sum(1:10) ```语言标注(lexer)从哪来?
指令后的r是语法高亮提示,其来源是源码中明确处理的逻辑:notebook_to_myst() 首先尝试从nb.metadata.language_info.pygments_lexer读取(第 372–375 行),读不到时才退回到default_lexer参数;写出代码单元格时仅在存在 lexer 的情况下才拼接后缀(第 400–401 行):
pygments_lexer = nb_metadata.get("language_info", {}).get("pygments_lexer", None) if pygments_lexer is None: pygments_lexer = default_lexer ... if pygments_lexer and cell.cell_type == "code": string += f" {pygments_lexer}"本例原始 Notebook 的language_info.pygments_lexer为r(见 ir_notebook.ipynb),所以转换产物中每个代码单元格都带r后缀。该后缀是可选的,仅为编辑器与渲染器提供语法高亮参考,不影响 Jupyter 内核的选择。
空单元格也被保留
第三个{code-cell} r指令内部没有代码内容,对应原始 Notebook 中那个空代码单元格。可见 Jupytext 的 MyST 转换不会丢弃空单元格,这对保持单元格索引、执行顺序与配对同步的一致性很重要。反向转换时 myst_to_notebook() 会把空 body 解析为空源码的代码单元格。
单元格元数据的两种写法
MyST 的单元格元数据支持两种形态,均由 dump_yaml_blocks() 控制输出:
- 无嵌套 dict 的紧凑形式——每行以冒号开头,适合简单参数:
```{code-cell} ipython3 :tags: [hide-output, show-input] print("Hallo!") ```- 含嵌套 dict 的围栏形式——用
---包裹完整 YAML:
```{code-cell} ipython3 --- other: more: true tags: [hide-output, show-input] --- print("Hallo!") ```对应的反向解析逻辑在 parse_directive_options():内容以---开头时按围栏 YAML 块解析,以:开头时按紧凑行解析,解析失败会抛出MystMetadataParsingError(测试见 test_ipynb_to_myst.py)。原始单元格(raw cell)使用相同的指令体系,例如 HTML 原始单元格写作:
```{raw-cell} :raw_mimetype: text/html <b>Bold text<b> ```五、Markdown 单元格与+++块分隔符
在 MyST 格式中,Markdown 单元格的内容原样写入、不做包裹。本例中This is a jupyter notebook that uses the IR kernel.就是直接落在 frontmatter 之后、第一个指令之前。
当相邻出现两个 Markdown 单元格,或某个 Markdown 单元格带元数据时,需要用+++块分隔符(block break)来切分。分隔符上方可附带一行的 JSON 元数据。仓库中的另一个 MyST 输出样本 Line_breaks_in_LateX_305.md 展示了典型用法:
This cell uses no particular cell marker $$ +++ This cell uses no particular cell marker, and a single slash in the $\LaTeX$ equation +++ This cell uses the triple quote cell markers...带元数据的写法在 markdown.md 文档中有说明,例如:
+++ {"slide": true} This is a markdown cell with metadata +++ This is a new markdown cell with no metadata从源码看,myst_to_notebook() 遇到myst_block_break类型的 token 时,会先冲刷当前待定 Markdown 文本为一个单元格,再读取+++行上的 JSON 作为下一个 Markdown 单元格的元数据(read_cell_metadata()负责 JSON 解析与 dict 类型校验)。因此+++既是单元格边界,也是 Markdown 单元格元数据的唯一载体。
六、从 ipynb 转换到 MyST:CLI 与 Python API
命令行方式
Jupytext 将myst注册为md:myst的别名(见 formats.py 中的映射"myst": "md:myst"),因此在仓库根目录下执行:
jupytext --to md:myst tests/data/notebooks/inputs/ipynb_R/ir_notebook.ipynb即可在当前目录生成同名ir_notebook.md。反向转换:
jupytext --to ipynb ir_notebook.md恢复出的.ipynb会保留原始内核信息与全部单元格(含空单元格)。需要注意的是:MyST 格式依赖markdown-it-py库,源码 raise_if_myst_is_not_available() 明确要求markdown-it-py~=1.0(Python >= 3.6),未安装时会抛出ImportError: The MyST Markdown format requires python >= 3.6 and markdown-it-py~=1.0。安装方式:
pip install jupytext markdown-it-pyPython API 方式
import jupytext nb = jupytext.read("tests/data/notebooks/inputs/ipynb_R/ir_notebook.ipynb") md = jupytext.writes(nb, fmt="md:myst") # 生成 MyST 文本 nb2 = jupytext.reads(md, fmt="md:myst") # 反向还原 Notebook测试 test_myst_representation_same_cli_or_contents_manager 专门验证了CLI、Python API、Jupyter Contents Manager 三条路径产出一致的文本:它先用jupytext_cli(["--to", "md:myst", ...])生成文本,再用jupytext.writes(nb, fmt="md:myst")生成文本,最后通过cm.formats = "ipynb,md:myst"让 Jupyter 服务器同步保存配对文件,三者用compare()严格比对相等。
配对(paired notebook)方式
在 Jupyter 中把.ipynb与.md配对,让二者在每次保存时自动同步,可以在配置文件中写入(参考仓库的 jupyter_config 示例):
formats = "ipynb,md:myst"保存.ipynb时 Jupytext 会同步写出对应的.md,之后你就可以直接在文本编辑器中修改 MyST 文档,改动同样会同步回 Notebook。
七、同一 Notebook 的三种 Markdown 变体对比
ir_notebook在仓库中恰好有三个 Markdown 形态的输出,是理解各格式差异的最佳教材:
| 格式 | 示例文件 | 代码单元格写法 | 元数据承载 |
|---|---|---|---|
| MyST Markdown | ipynb_to_myst/ir_notebook.md | ```{code-cell} r | YAML 块 /:key: value行 |
| Jupytext Markdown | ipynb_to_md/ir_notebook.md | ```R | 行内key=value |
| Pandoc Markdown | ipynb_to_pandoc/ir_notebook.md | ::: {.cell .code}内嵌```R | Pandoc div 属性 |
三者都从同一个 R 内核 Notebook 生成,frontmatter 几乎一致,差别集中在单元格编码上。Jupytext Markdown 的```R写法最接近普通 Markdown,适合 GitHub 直接渲染;MyST 的{code-cell}指令是 MyST-NB / Jupyter Book 生态的标准接口;Pandoc 变体则面向 Pandoc 文档转换流水线。仓库中的 demo/World population.myst.md 还提供了内容更丰富的 MyST Notebook 实例供参考。
八、源码级原理:双向转换的核心路径
MyST 转换的核心实现在 src/jupytext/myst.py,两个主函数构成完整闭环:
myst_to_notebook(text, ...)(读方向):用
markdown-it-py解析器(get_parser(),启用table、front_matter、myst_block、myst_role插件)把文本 token 化,然后遍历 token:frontmatter 转为 Notebook 元数据;{code-cell}围栏转为代码单元格(解析选项、取 lexer、校验同语言一致性);{raw-cell}转为原始单元格;myst_block_break切分并读取 Markdown 单元格元数据。若开启add_source_map=True,还会在元数据中写入source_map——每个单元格起始源码行号的列表(测试见 test_add_source_map)。notebook_to_myst(nb, ...)(写方向):先 dump Notebook 元数据为 YAML frontmatter;遍历单元格——Markdown 单元格按需前插
+++(带元数据或紧邻前一个 Markdown 单元格时)后原样写出;代码/原始单元格用围栏包裹,代码单元格附上 pygments lexer,单元格元数据交给dump_yaml_blocks()按紧凑/围栏两种形态输出;遇到源码中含三个及以上反引号时,cell_to_text.py 中的three_backticks_or_more()会自动升级为四个反引号以免歧义。
值得注意的细节是语言一致性校验:读入时若多个代码单元格的 lexer 不一致,myst_to_notebook()会发出All code cells in a MyST notebook must have the same language警告;且当 Notebook 没有language_info时,首个 lexer 会被记录为jupyter.jupytext.default_lexer元数据(myst.py),同时写入notebook_metadata_filter: -all防止冗余元数据回流。
九、测试与回归保障
MyST 格式在仓库中有完整的测试覆盖,除上文提到的解析错误与一致性测试外,test_ipynb_to_myst.py 还包含:
test_matches_mystnb()(第 86–136 行):验证格式识别函数对 frontmatter、指令、扩展名的判定规则;test_meaningfull_error_write_myst_missing/test_meaningfull_error_open_myst_missing(第 173–208 行):未安装markdown-it-py时,CLI 与 Contents Manager 两条路径都会给出明确的ImportError提示;test_not_installed()(第 139–143 行):格式注册表中无 MyST 时抛出JupytextFormatError。
此外 tests/functional/round_trip/test_mirror.py 与 test_myst_header.py 等轮换测试(round-trip)持续验证.ipynb → .md → .ipynb的往返无损性——这正是 MyST 文档可以安全参与 git 工作流的前提。
十、典型应用场景与延伸阅读
掌握 MyST Notebook 之后,你可以把它接入以下工作流:
- 文档即代码(docs-as-code):用
git diff审查 Notebook 变更,消除.ipynbJSON 难读、易冲突的痛点; - Jupyter Book / MyST-NB 出版:MyST 文档可直接被 Sphinx 生态构建为静态站点或 PDF,无需手工转换;
- 多格式配对:
formats = "ipynb,md:myst"让团队中偏好 Jupyter 的成员与偏好文本编辑器的成员协作同一份内容; - 多语言支持:本例展示了 R 内核的完整流程,Jupytext 对 Python、Julia、R 及众多 Jupyter 支持的语言一视同仁。
延伸阅读建议:完整格式规范见 website/src/content/docs/formats/markdown.md;格式注册与别名映射见 src/jupytext/formats.py;若需在 VS Code 中获得更好的 MyST 语法高亮,可安装官方文档中提到的myst-highlight扩展。
以上所有示例均来自当前仓库的真实文件与源码,你可以直接 clone 本仓库后在本地重现转换过程,或参考tests/data/notebooks/outputs/ipynb_to_myst/目录下的全部输出样本进行对比学习。
- 开发工具
【免费下载链接】jupytext
Jupyter Notebooks as Markdown Documents, Julia, Python or R scripts
相关推荐
Jupytext 将 IR 内核 R 语言 Notebook 转换为 Markdown 文档:ir_notebook 示例深度解析
Jupytext 将 IR 内核 R 语言 Notebook 转换为 Markdown 文档:ir_notebook 示例深度解析 本文以仓库内真实测试样例 i
开发工具Jupytext 的 MyST Markdown 格式:从 Jupyter Notebook 到 Markedly Structured Text 的双向转换指南
Jupytext 的 MyST Markdown 格式:从 Jupyter Notebook 到 Markedly Structured Text 的双向转换指
开发工具Jupytext 实战:gnuplot Notebook 与 MyST Markdown 的相互转换——格式结构拆解与源码级原理
Jupytext 实战:gnuplot Notebook 与 MyST Markdown 的相互转换——格式结构拆解与源码级原理 导读:本文以 tests/da
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考