☰
Jupytext 实战:将 SAS Notebook 转换为 MyST Markdown 文档
2026/10/8 1:31:21 网站建设 项目流程
  • 开发工具

【免费下载链接】jupytext

Jupyter Notebooks as Markdown Documents, Julia, Python or R scripts

项目地址:https://gitcode.com/gh_mirrors/ju/jupytext
点击查看免费下载

本指南以 Jupytext 仓库中的真实示例 sas.md 为主线,完整讲解 Jupyter 生态中 SAS 内核笔记本(.ipynb)如何被表示为 MyST Markdown 文档、两种形态之间如何无损互转,以及 jupytext 在底层如何识别与处理 SAS 语言。读完本文,你将能读懂 MyST 格式的 SAS 文本笔记本结构、理解{code-cell}指令与 YAML front matter 的作用,并能用 jupytext 在自己项目中完成.ipynb与.md的转换与镜像同步。

一、从一份 SAS Notebook 到 MyST 文档:示例全景

仓库测试数据中,tests/data/notebooks/inputs/ipynb_sas/sas.ipynb是一个包含 SAS 内核元数据、1 个 Markdown 单元和 4 个代码单元的笔记本,其内容围绕 SAS 基础操作设计:proc sql查询sashelp.cars数据集、宏变量%let/%put的定义与引用、以及一个完整的%macro宏定义与调用。它被 jupytext 转换后的 MyST 表示保存在 sas.md 中,全文如下:

--- kernelspec: display_name: SAS language: sas name: sas --- # SAS Notebooks with jupytext ```{code-cell} proc sql; select * from sashelp.cars (obs=10) ; quit; ``` ```{code-cell} %let name = "Jupytext"; ``` ```{code-cell} %put &name; ``` ```{code-cell} /* Note when defining macros "%macro" cannot be the first line of text in the cell */ %macro test; data temp; set sashelp.cars; name = "testx"; run; proc print data = temp (obs=10); run; %mend test; %test ```

这份文件本身就是一篇自洽的“文本笔记本”:它既可以被任何 Markdown 渲染器正常阅读,又可以被 jupytext 反向解析回与原始.ipynb完全等价的 notebook 对象。这正是 MyST 格式(myst)的定位——在可读性与机器可解析性之间取得平衡。

二、MyST 文本笔记本的结构拆解

2.1 YAML front matter:笔记本级元数据

文件开头的---包裹块是文档级元数据(front matter)。对照源码 myst.py 中的myst_to_notebook实现:解析器(基于markdown-it-py的 front matter 插件)会将该 YAML 块整体提取出来,作为 notebook 的顶层metadata写入。本例中的三个字段:

字段值含义
kernelspec.display_nameSASJupyterLab 界面中显示的内核名称
kernelspec.languagesas内核对应的语言标识
kernelspec.namesas内核注册名,用于定位sas内核启动器

这三个字段与原始 ipynb 的metadata.kernelspec一一对应(可对照 sas.ipynb)。当 MyST 文档被反向读回并交给 Jupyter 运行时,这些信息决定了 notebook 会绑定哪个内核。

值得注意:在该示例的 MyST 版本中,front matter 直接以kernelspec为顶层键;而在 R Markdown(.Rmd)等格式中,同一份元数据会被包进jupyter:命名空间(参见 sas.Rmd 的写法)。这是不同文本格式对 notebook 元数据的组织约定差异,语义完全等价。

2.2{code-cell}指令:代码单元

MyST 使用带指令标注的围栏代码块表示代码单元。{code-cell}正是 myst.py 中定义的CODE_DIRECTIVE常量。解析逻辑(myst.py)按如下方式工作:

  • 解析器扫描顶层 token,凡是fence(围栏块)且其 info 以{code-cell}开头的,即判定为一个代码单元;
  • 指令中{code-cell}之后的文本(如{code-cell} sas)会被作为语法高亮语言(lexer)处理;若所有代码单元语言一致,该 lexer 会被写入 notebook 元数据中的jupytext.default_lexer(myst.py);
  • 围栏内的全部文本行拼接为该代码单元的source;
  • 出现在代码单元之前的普通 Markdown 文本会被收集为 Markdown 单元(对应 sas.md 中的# SAS Notebooks with jupytext标题)。

反方向(notebook → MyST)由notebook_to_myst(myst.py)完成:它遍历 notebook 单元,将代码单元输出为{code-cell}围栏块,并把单元格级元数据以 YAML 或:key: value形式写在指令体内部。这是镜像往返测试能成立的基础。

三、示例中的 SAS 代码单元逐一解读

该示例的四个代码单元覆盖了 SAS 编程的三种典型形态,也顺便验证了 jupytext 对%宏指令文本的原样保留能力。

3.1 SQL 过程步

proc sql; select * from sashelp.cars (obs=10) ; quit;

这是 SAS 中通过proc sql直接执行 SQL 查询的典型写法,读取 SAS 自带示例数据集sashelp.cars的前 10 行。文本笔记本中它被原样存储,没有任何转义或改写。

3.2 宏变量定义与引用

%let name = "Jupytext";
%put &name;

%let定义宏变量,&name引用它,%put将解析后的值打印到日志。%是 SAS 宏语言的关键前缀,也是 jupytext 文本表示中最需要小心的字符之一。源码 magics.py 中有一个针对性处理:对于octave、matlab、sas、logtalk等语言,行首的%不会被误判为需要注释掉的 Jupyter magic——这正是 SAS 宏代码能在文本与 ipynb 之间逐字符往返而不损坏的根本保证。

3.3 宏定义单元(含重要注释)

/* Note when defining macros "%macro" cannot be the first line of text in the cell */ %macro test; data temp; set sashelp.cars; name = "testx"; run; proc print data = temp (obs=10); run; %mend test; %test

这个单元示范了两件事:

  1. SAS 块注释:.sas语言在 jupytext 中登记的注释符是/*、*/(见下文第 4.1 节),因此示例使用/* ... */书写说明文字;
  2. 宏定义的边界约束:单元内明确指出,定义宏时%macro不能作为单元文本的第一行——必须在前面先放一条注释。这是 SAS 内核解析文本单元的实操经验,也是该示例特意保留注释的原因:它确保代码单元被真正执行而不是被当作特殊指令处理。

四、源码级深度解析:jupytext 如何认识 SAS

4.1 SAS 在语言注册表中的位置

jupytext 支持的所有脚本语言集中登记在 languages.py 的_SCRIPT_EXTENSIONS字典中。SAS 的登记条目为:

".sas": { "language": "sas", "comment": "/*", "comment_suffix": "*/", },

这告诉 jupytext 三件事:.sas扩展名对应的语言名是sas;在 percent / light 等“注释式”文本格式中,行注释使用/* ... */块注释。同时,sas也出现在_JUPYTER_LANGUAGES列表(languages.py)中,意味着它既可作为 notebook 的主语言,也可作为单元格级 magic 语言被识别。此外 languages.py 的usual_language_name把"sas"规范化为"SAS",用于与内核声明的语言做大小写无关的等价比较。

4.2 主语言判定与单元格语言

default_language_from_metadata_and_ext 决定文本笔记本的主语言:优先取metadata.jupytext.main_language,其次取metadata.kernelspec.language,最后回退到扩展名映射。SAS 与 R 一样被单独列出、直接返回原值而不做小写化——这保证kernelspec.language: sas在转换中不会被改写。当 notebook 没有设置内核时,set_main_and_cell_language 会统计各单元格语言、取多数者为main_language并写入jupytext元数据,确保后续其他格式的转换仍然稳定。

4.3 MyST 格式的完整解析/生成流程

MyST 的读写主逻辑都在 myst.py 中:

  • 读取方向(myst_to_notebook,myst.py):用markdown-it-py配合front_matter_plugin、myst_block_plugin、myst_role_plugin解析文本 → 提取 front matter 为 notebook 元数据 → 按 token 顺序把普通 Markdown 文本 flush 为 Markdown 单元、把{code-cell}/{raw-cell}围栏块解析为代码/原始单元 → 支持+++分块标记承载 Markdown 单元元数据。若解析 YAML 失败会抛出MystMetadataParsingError并附带行号定位(myst.py)。
  • 写出方向(notebook_to_myst,myst.py):将 notebook 元数据经dump_yaml_blocks(myst.py)压缩为 YAML 块;代码单元写成{code-cell}围栏,且会智能选择不少于三反引号的围栏分隔符(three_backticks_or_more)以避免与单元内容中的反引号冲突。

当.md文件同时满足“以---开头 + 含{code-cell}指令”等特征时,matches_mystnb 会判定其为 MyST 文本笔记本——这正是 jupytext 在打开文件时自动选择myst格式的依据。

五、同源多格式对照:一份 SAS Notebook 的多种文本形态

仓库的 outputs 目录保存了同一份 SAS ipynb 在不同文本格式下的镜像输出,是理解 jupytext 格式矩阵的最佳材料:

格式输出文件代码单元表示元数据表示
MyST Markdownipynb_to_myst/sas.md{code-cell}围栏块顶层 YAML front matter
Markdownipynb_to_md/sas.md普通 ```sas 围栏块YAML front matter
R Markdownipynb_to_Rmd/sas.Rmd{sas}围栏块jupyter:命名空间下的 front matter
Percent 脚本ipynb_to_percent/sas.sas/* %% */单元分隔注释 + 块注释/* ... */块注释包裹的 YAML
Hydrogen 脚本ipynb_to_hydrogen/sas.sas/* %% */分隔注释块注释包裹的 YAML
Light 脚本ipynb_to_script/sas.sas空行分隔块注释包裹的 YAML

以 ipynb_to_percent/sas.sas 为例,可以看到.sas语言下的“注释式”写法:由于 SAS 的注释符是/*、*/(来自 4.1 节的注册表),jupytext 用/* %% */标记单元边界、用/* --- */ ... /* --- */包裹 YAML 元数据,而%let、%put、%macro等宏代码行则完全原样保留、不做注释化处理(与 3.2 节的 magic 豁免逻辑一致)。

六、测试如何保障 SAS 往返无损

SAS 示例文件的生成与验证由镜像测试体系保障。test_mirror.py 中的test_ipynb_to_myst对所有tests/data/notebooks/inputs下的 ipynb(包括ipynb_sas/sas.ipynb)执行:

@pytest.mark.requires_myst def test_ipynb_to_myst(ipynb_file, no_jupytext_version_number): assert_conversion_same_as_mirror(ipynb_file, "md:myst", "ipynb_to_myst")

assert_conversion_same_as_mirror会做两件事:一是把 ipynb 转成 MyST 文本并与outputs/ipynb_to_myst/目录下的基准文件逐字节比对;二是把转换结果再读回 ipynb,验证与原 notebook 等价。这两个方向共同保证“文本是 ipynb 的忠实镜像”,防止新版本发布时产生无意的格式漂移。同一文件还参与test_ipynb_to_md、test_ipynb_to_Rmd、test_ipynb_to_percent等其它格式的镜像测试(test_mirror.py),因此 SAS 示例在多格式间的行为都被持续回归监控。

七、在自己的项目中复现与使用

  1. 查看示例:直接阅读 sas.md 了解 MyST 文本笔记本的排版规范;对照 sas.ipynb 观察两种形态的对应关系。

  2. 命令行转换(在已安装 jupytext 与markdown-it-py的环境中):

# ipynb -> myst(md 文件带 {code-cell} 指令即视为 myst 格式) jupytext --to md:myst sas.ipynb -o sas.myst.md # myst -> ipynb jupytext --to ipynb sas.myst.md -o sas.ipynb

md:myst中的myst是格式名(myst_extensions允许.md、.myst、.mystnb、.mnb扩展名,见 myst.py);MyST 格式要求环境提供markdown-it-py及三个 mdit 插件(myst.py),否则会抛出明确的导入错误提示。

  1. 配对同步:为让 ipynb 与 MyST 文本保持实时镜像,可在jupytext配置中声明配对格式,例如在jupytext.toml中设置formats = "ipynb,myst",此后 Jupyter 中保存 notebook 会自动同步写两份文件;文本文件的编辑也会触发 ipynb 的更新。这也是本仓库 demo 目录(如demo/World population.myst.md)所演示的日常工作流。

八、小结

通过 sas.md 这一示例可以看到,jupytext 对 SAS 笔记本的 MyST 化表达已经形成完整闭环:语言注册表(languages.py)负责识别.sas扩展名与sas内核、豁免%宏指令被误注释(magics.py);MyST 模块(myst.py)负责 front matter、{code-cell}指令与单元格的双向解析/生成;镜像测试(test_mirror.py)则持续验证 ipynb ↔ MyST 之间不丢失任何单元格、元数据与 SAS 宏代码。对于需要在 Git 中版本管理 SAS 分析流程、或用 Markdown 撰写可执行的 SAS 文档的团队,这套文本表示提供了既适合人读、又适合机器往返的中间形态。

  • 开发工具

【免费下载链接】jupytext

Jupyter Notebooks as Markdown Documents, Julia, Python or R scripts

项目地址:https://gitcode.com/gh_mirrors/ju/jupytext
点击查看免费下载
上一篇:本地运行LLM的终极方案:ChatMLX如何保障你的数据隐私安全
下一篇:Happy Island Designer数据持久化方案:本地存储与图像编码的巧妙实现 🏝️

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询