版本控制,也被称为源代码控制,用于跟踪软件开发和数据科学工作中的代码和其他工件的变化。版本控制允许对工件进行检查点,比较不同的版本,并分支到不同的开发路径。我们大多数人已经知道版本控制,但如何在Jupyter笔记本中有效地使用它并不明显。由于Jupyter笔记本只是包含代码、元数据和输出的JSON文档,在使用git等标准版本控制工具时,比较笔记本的版本可能会很笨重。但对于使用Jupyter笔记本的开发者和数据科学家来说,有一些方法可以使生活变得更容易。
一个简单的差异例子告诉我们它有多混乱
理解这些问题的最好方法是看一下简单的例子。在这个例子中,我将演示Jupyter笔记本的三个部分如何在开发过程中改变数值,以及这些文件如何与默认的git工具互动。
如果你想跟着学,你可以手动创建一个笔记本和git仓库,模仿下面的代码和例子,或者你可以克隆我的git仓库–特别是编辑测试笔记本,然后用它来工作。
在这个例子中,我们用下面的代码做了一个简单的笔记本。
import matplotlib.pyplot as plt
plt.plot([x**2 for x in range(100)])
x**2的Matplotlib图
如果我们执行这个笔记本,保存它,然后把它添加到git,我们就有了我们的基础版本。
> git add jupyter_git_example.ipynb
> git commit -m "initial git commit for example, for diffing"
我不会在这里展示笔记本的全部源代码,但你可以自己看一下源代码(使用文本编辑器或unix shell中的less
),你会发现它只是一个JSON文档。但如果我们重新执行包含代码的单元格并保存笔记本,会发生什么?有什么变化?这就是我所看到的差异。
> git diff jupyter_git_example.ipynb
diff --git a/tools/jupyter_git_example.ipynb b/tools/jupyter_git_example.ipynb
index 906fb11..750fe85 100644
--- a/tools/jupyter_git_example.ipynb
+++ b/tools/jupyter_git_example.ipynb
@@ -2,17 +2,17 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
- "id": "28c01064",
+ "execution_count": 2,
+ "id": "e2c08a0a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "[<matplotlib.lines.Line2D at 0x1187977c0>]"
+ "[<matplotlib.lines.Line2D at 0x11891e100>]"
]
},
- "execution_count": 1,
+ "execution_count": 2,
"metadata": {},
"output_type": "execute_result"
},
@@ -33,6 +33,14 @@
"import matplotlib.pyplot as plt\n",
"plt.plot([x**2 for x in range(100)])"
]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2915ced8",
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
],
"metadata": {
这里发生了什么?你可以看到,单元格的执行数发生了变化(还有一些标识符)。它还在最上面的单元格下面添加了一个新的空白单元格(在我执行第一个单元格时创建)。实际上没有代码发生变化,但我们必须非常仔细地看才能弄清楚,如果你以前没有见过笔记本文件,这可能会让人困惑。
视觉输出,特别是图像,使差异变得更加混乱
现在,如果我改变代码(比如从x**2
到x**3
)并执行和保存笔记本,代码和输出都会改变。
对我来说,它看起来像这样
> git diff jupyter_git_example.ipynb
diff --git a/tools/jupyter_git_example.ipynb b/tools/jupyter_git_example.ipynb
index 906fb11..6a8084e 100644
--- a/tools/jupyter_git_example.ipynb
+++ b/tools/jupyter_git_example.ipynb
@@ -2,23 +2,23 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
- "id": "28c01064",
+ "execution_count": 3,
+ "id": "57e4b20e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "[<matplotlib.lines.Line2D at 0x1187977c0>]"
+ "[<matplotlib.lines.Line2D at 0x11897a160>]"
]
},
- "execution_count": 1,
+ "execution_count": 3,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYMAAAD4CAYAAAAO9oqkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZ
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAEDCAYAAAAlRP8qAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZ
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
@@ -31,8 +31,16 @@
],
"source": [
"import matplotlib.pyplot as plt\n",
- "plt.plot([x**2 for x in range(100)])"
+ "plt.plot([x**3 for x in range(100)])"
]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "09023a82",
+ "metadata": {},
+ "outputs": [],
+ "source": []
像上次一样,执行次数和ID都发生了变化,但现在在diff中既有代码的变化,也有输出的变化。另外,由于matplotlib图像是base64编码的,所以很难说出什么变化–是输出中的image/png
。如果我们想让我们的差异尽可能的干净,我们应该怎么做?我使用两种策略中的一种,选择使用每种策略有不同的理由。
简单的策略。只把笔记本当作源代码
最简单的策略是只提交没有输出的已清除笔记本。要做一个没有输出的已清除的笔记本,选择内核菜单,并选择 “重启和清除输出”。如果你保存这个笔记本,你会看到笔记本文件中没有输出,唯一出现的差异是代码和元数据。这种策略的优点是你不会处理混乱的输出数据的差异,特别是视觉数据。你的差异(可能还有大多数合并)将不会很难理解。
对于笔记本的某些用途,这将有很大的意义。例如,如果你的笔记本是用于papermill的参数化笔记本,你有一个基本的笔记本,用于使用不同的参数创建几个甚至几千个不同的笔记本。保存其中一个参数的随机输出不一定有什么帮助,而且保存这些笔记本的每个版本通常也没有意义。然而,保存基本版本(没有输出)可能是一个不错的选择。这个方案主要是把笔记本当作源代码,而不是当作数据或结果。
如果你使用这个选项,你可能要考虑为你的笔记本代码创建单元测试。你也可以考虑把它拉出到一个Python模块中(在一个.py文件中),如果这对你的用例有意义的话。
替代策略。将笔记本视为代码和数据
理想情况下,你想把你的笔记本和结果一起保存,这确实是使用笔记本的主要原因之一
nbdime使差异和合并更加容易
如果你把一个笔记本同时当作代码和数据,你可以使用一个简单的工具来使你的生活更容易。安装nbdime工具–笔记本的差异和合并。这个工具是Jupyter项目的一部分,它知道如何正确地对笔记本文件进行比较,并与用于版本控制的git或mercurial,以及Jupyter服务器和JupyterLab以及其他工具集成。
安装
你可以用pip安装nbdime。理想情况下,你会在一个开发笔记本的虚拟环境中进行安装。
pip install nbdime
简单的差异
要比较一个笔记本,只需运行nbdiff。我在运行这个命令之前,先重新运行了笔记本并保存了它(因为我之前已经清除了输出)。对于这样一个小的变化,输出有点冗长,但它被分成了几个部分。你可以看到,单元格ID、单元格输出的文本、单元格的图形输出和单元格的来源都有变化。它还表明在笔记本的最后有一个新的代码单元。这对人来说更容易理解!
nbdiff jupyter_git_example.ipynb
nbdiff tools/jupyter_git_example.ipynb (HEAD) tools/jupyter_git_example.ipynb
--- tools/jupyter_git_example.ipynb (HEAD) (no timestamp)
+++ tools/jupyter_git_example.ipynb 2021-09-02 17:25:50.889444
## modified /cells/0/id:
- 28c01064
+ 2f344fee
## modified /cells/0/outputs/0/data/text/plain:
- [<matplotlib.lines.Line2D at 0x1187977c0>]
+ [<matplotlib.lines.Line2D at 0x10f789a60>]
## inserted before /cells/0/outputs/1:
+ output:
+ output_type: display_data
+ data:
+ image/png: iVBORw0K...<snip base64, md5=86ab879409f2b42a...>
+ text/plain: <Figure size 432x288 with 1 Axes>
+ metadata (unknown keys):
+ needs_background: light
## deleted /cells/0/outputs/1:
- output:
- output_type: display_data
- data:
- image/png: iVBORw0K...<snip base64, md5=d93fe1c011d9afe8...>
- text/plain: <Figure size 432x288 with 1 Axes>
- metadata (unknown keys):
- needs_background: light
## modified /cells/0/source:
@@ -1,2 +1,2 @@
import matplotlib.pyplot as plt
-plt.plot([x**2 for x in range(100)])
+plt.plot([x**3 for x in range(100)])
## inserted before /cells/1:
+ code cell:
视觉差异
还有一个基于网络的差异工具,它允许你将变化可视化。
nbdiff-web jupyter_git_example.ipynb
这将在一个新的浏览器标签或窗口中启动一个可视化差异工具。对于我上面的笔记本例子,它看起来是这样的。
使用nbdiff-web的可视化差异
与Git集成
你可以配置git来使用nbdiff工具对笔记本文件进行比较。
从你的git项目的根部,运行enable命令。
nbdime config-git --enable
默认情况下,它只在当前版本库中启用 nbdime。你可以全局启用它 (--global
) 或在系统级启用 (--system
),你也可以禁用它 (--disable
)。一旦启用,git diff 将使用 nbdiff 格式输出。
合并
一旦你在版本控制中跟踪你的笔记本的变化,你很可能会遇到这样的情况:你必须合并另一个版本的变化–要么是你创建的分支,要么是其他人做的变化,并作为另一个.ipynb文件交付给你。如果你试图用默认的git工具来合并结果,或者用JSON文件本身来手工合并,这可能是一个难以置信的麻烦。在某些合并中,git会在JSON文件中间的代码中插入合并冲突标记(即<<<<
和=====
),在这些地方使其成为一个无效的文件。如果是这样的话,它就不能被加载到任何正常的Jupyter笔记本编辑工具中,它将不得不被检查并手工修复。
对于一个只是合并两个笔记本的简单例子,我复制了上面的笔记本,但在一个地方改变了源代码,然后将其保存为jupyter_git_example2.ipynb
。合并工具知道如何将这两个改动合并成一个有效的笔记本文件,我可以用Jupyter打开。
nbmerge jupyter_git_example.ipynb jupyter_git_example2.ipynb > merged.ipynb
如果我在Jupyter中打开merged.ipnb
,我看到代码单元包含了合并后的代码–它有一个冲突,我可以轻松解决。
import matplotlib.pyplot as plt
<<<<<<< local
plt.plot([x**3 for x in range(100)])
=======
plt.plot([x**4 for x in range(100)])
>>>>>>> remote
合并工具支持更多的选项,并且可以进行三向合并。它还支持一个可视化工具,就像nbdiff-web。当合并工具与git集成并在git工作流程中使用时,它们是最有用的。
例如,你可能会选择建立一个特性分支,并在一个笔记本上做一些工作。然后,你可能最终在主分支上做了一些修改。这时就需要进行合并,把这些改动移到主干分支上。nbdime的合并工具知道如何在笔记本的背景下进行这种合并。你也可以用视觉方式进行合并。
下面是一个带注释的例子,说明我们通常会遇到这种情况,这可能是一个或几个开发人员在多个分支上提交。
> git branch nbdime-example # make a feature branch
> git checkout nbdime-example # checkout branch
# work on this branch, make a notebook change, save the notebook
> git add jupyter_git_example.ipynb
> git commit -m "changes on feature branch"
> git checkout main # switch back to main branch
# make a change to jupyter_git_example.ipynb, but in the same lines as on nbdime-example branch
> git add jupyter_git_example.ipynb
> git commit -m "changes on main branch"
这时,我们可能想合并这两个分支。一旦我们安装了nbdime,我们就可以使用他们的diff工具来更好地了解这些变化。
> git checkout master
> git merge nbdime-example
# get message about merge conflicts on command line
> git mergetool --tool=nbdime
# now we see the merges in context
你可以查看文档,了解更详细的配置。你也可以在你的Jupyter笔记本菜单栏中启用一个nbdiff按钮(在Jupyter笔记本或JupyterLab中)。如果你不想在命令行中运行差异,这可能是最好的选择。完整的说明在这里。请注意你是想只在你的虚拟环境中安装扩展,还是在整个系统中安装。
一些最佳实践
在版本控制中使用Jupyter笔记本时,以下是对我来说相当有效的方法。请注意,更大的工作组可能需要更严格的标准,但一般来说,这些准则对我来说很有效。
尽可能多地将代码提取到Python文件中
将代码放在Python文件中会使它更容易被访问、测试和广泛使用。使用像git这样的版本控制工具,它也更容易进行差异和合并。但对于某些代码来说,把它放在笔记本中更方便。给予笔记本作者灵活性可能是值得的。所以强迫所有的 Python 代码进入单独的 Python 文件并不总是合理的。
在提交笔记本之前,重新启动它并重新运行它,然后保存。
笔记本的开发可能会引入错误,因为单元格有时会在运行后被打乱顺序或删除。这是一个好主意,从头开始重新运行笔记本。这可以确保所有的代码都在笔记本中,并以正确的顺序运行。对于运行时间很长的笔记本,重新运行可能并不实用。你更好的选择是将笔记本分成多个小的笔记本。选择不运行一个完整的笔记本是自找麻烦。一个额外的好处是,大多数单元的元数据(如执行数)在同一文件的提交之间不会改变。
如果一个笔记本是一个模板,清除输出并保存
如果一个笔记本是供papermill使用的参数化笔记本,我通常在保存时不保留输出。这些笔记本是一个有多个输入的模板运行,所以你可以把它们当作简单的代码。
使用nbdime来区别和合并Jupyter笔记本代码
即使你不需要使用合并工具,只要看到一个漂亮的diff,显示代码的变化,对较大的变化就有很大帮助。如果你在笔记本开发的虚拟环境中安装nbdime,你会发现额外的信息和便利在提交修改、恢复代码或与合作者分享笔记本时非常有帮助。
尽管笔记本格式确实很有帮助,因为它将代码与输出保持在一起,但跟踪笔记本的变化可能是一件很痛苦的事情。但我们可以使用策略和工具来帮助我们很好地利用版本控制工具。
The postVersion control for Jupyter notebooksappeared first onwrighters.io.
今天的文章Jupyter笔记本的版本控制分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/23081.html