在VSCode中使用Jupytext实现ipynb与py文件转换

对于经常需要使用Python进行数据分析与处理的人来说,Jupyter Notebook是一个非常不错的选择。但Notebook文件在版本管理中,常常会造成麻烦。

这是因为,Notebook的.ipynb文件格式上实质是一个Json文件,包含了相当多的辅助信息,记录了诸如单元格类型、执行次数、单元格输出等信息,且源代码实质上已经被转义成Json兼容的字符串数组

这一特性导致,在使用Git之类版本管理工具管理Notebook源代码时,其实并不方便。举例来说,即便是切换了Notebook运行的Kernel,也会修改Notebook文件,在版本管理工具中就是一次文件改动。

显然,大多数情况下,我们希望追踪的是Notebook中的源代码,单元格输出等信息并不是使用Git等工具管理代码仓库的目标。

因此,我们是否能找到一种方法缓解上述问题呢?

用Python文件存储不就好了?

是的!非常正常的想法,既然在Notebook中我们编写的都是.py代码,那么为什么我们不能将Notebook直接存储为.py文件呢?

事实上,无论是Jupyter的浏览器客户端,还是nbconvert模块,抑或是VSCode之类的编辑器,都提供了将Notebook转换为Python源代码文件的功能。

并且,只需要辅助以正确的助记符,包括Markdown单元格等信息,都可以被正常存储。

怎么转换回Jupyter Notebook?

存储的问题解决了,但怎么从.py文件转回.ipynb文件呢?

这时候就需要一个名为jupytext的模块了。详情请看:https://jupytext.readthedocs.io/en/latest/

手动转换好麻烦

不过,设想一下,如果用手动的方法,我们每次编辑一个文件将包含以下几个流程:

  1. .py转换成.ipynb
  2. .ipynb文件上编程和调试
  3. 一切OK后,将.ipynb转换为.py
  4. 在Git中提交更改

很麻烦对吧?有没有能帮我们自动做这些的程序呢?

当然有!jupytext本身就提供了将.ipynb.py配对的能力。

具体来说,只要将一个.ipynb文件与指定的格式进行配对,配对完成后,当修改两个文件的其中一个,同步一下,另一个文件也将自动反映出最新更改。

这样一来,只需要在Git仓库里追踪.py文件,然后尽情修改.ipynb,所有更改都会自动同步。我们就实现了,在.ipynb文件中调试,用.py文件存储的目标。

而所谓的配对,其实也没什么特别的,无非就是在配对文件.ipynbmetadata里,写入配对的格式信息,例如一个最简单的HelloWorld Notebook在配对后,大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(\"Hello World!\")"
]
}
],
"metadata": {
"jupytext": {
"formats": "ipynb,py:percent"
},
"kernelspec": {
"display_name": "myvenv",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.12.7"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

详情参见上述jupytext文档中的示例。

VSCode里怎么办呢

然而,jupytext的配对功能仅限于网页客户端的原生Jupyter,VSCode似乎并没有对这一功能的支持,因此似乎只能每次手动敲命令进行转换。

等一等,敲命令?那能不能让VSCode自动为我们执行转换的命令呢?别忘了VSCode的任务系统!

具体来说,我们需要做以下工作:

使用命令实现配对

由于配对是需要显示执行的,因此我们首先用命令实现这一点:

1
2
3
4
jupytext --set-formats ipynb,py:percent test-hello.ipynb

# Wildcard is OK for multiple files
jupytext --set-formats ipynb,py:percent *.ipynb

注意到上述命令中的ipynb,py:percent了吗?它的意思是将一个.ipynb文件与 percent 格式的.py文件配对起来。

什么是 percent格式呢?实际上就是通过# %%等助记符作为Notebook单元格的划分,方便阅读也方便将.py转换回.ipynb文件。这一格式的介绍也请阅读jupytext的文档啦。

利用VSCode Tasks实现一键同步

现在,我们可以试着借助VSCode Tasks,实现.py.ipynb文件内容更改的一键同步。

编辑工作路径.vscode/tasks.json文件(没有就新建一个),编辑内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "JupyText Sync",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": [
"-m",
"jupytext",
"--sync",
"${file}"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

保存文件。

现在,每当我们在编辑.ipynb.py文件的其中一个并保存后,只要我们按下Ctrl + Shift + B键,运行VSCode的构建任务,我们配对的同名文件就会自动同步更改。