Using IPython notebooks under version control
什么是保持ipython笔记本在版本控制下的好策略?
笔记本的格式对于版本控制来说是相当合适的:如果你想对笔记本和输出进行版本控制,那么这就非常有效。当人们只想对输入进行版本控制时,麻烦就来了,不包括单元输出(aka)。构建产品"),可以是大的二进制斑点,尤其是电影和情节。特别是,我正试图找到一个好的工作流:
- 允许我在包括或排除输出之间进行选择,
- 如果我不想要的话,可以防止我意外地提交输出,
- 允许我在本地版本中保留输出,
- 允许我使用版本控制系统查看输入的更改时间(即,如果我只对输入进行版本控制,但本地文件有输出,那么我希望能够查看输入是否发生了更改(需要提交)。使用版本控制状态命令将始终注册差异,因为本地文件有输出。)
- 允许我从更新的干净笔记本更新工作笔记本(包含输出)。(更新)
如前所述,如果我选择包括输出(例如,在使用nbviewer时是可取的),那么一切都很好。问题是当我不想版本控制输出时。有一些工具和脚本用于剥离笔记本的输出,但我经常遇到以下问题:
我已经考虑了下面要讨论的几个选项,但还没有找到一个好的全面的解决方案。完整的解决方案可能需要对IPython进行一些更改,或者可能依赖于一些简单的外部脚本。我目前使用的是Mercurial,但我希望有一个同样适用于Git的解决方案:一个理想的解决方案是版本控制不可知论。
这个问题已经讨论了很多次,但是从用户的角度来看,还没有确定的或明确的解决方案。这个问题的答案应该提供明确的策略。如果它需要最新(甚至是开发)版本的IPython或易于安装的扩展,那就很好了。
更新:我一直在玩我的修改过的笔记本版本,它可以选择保存一个
笔记移除(剥离)输出
- 当笔记本电脑运行时,可以使用
Cell/All Output/Clear 菜单选项删除输出。 - 有一些用于删除输出的脚本,例如用于删除输出的脚本nbstripout.py,但不会生成与使用笔记本界面相同的输出。这最终包括在ipython/nbconvert repo中,但这已关闭,说明更改现在包含在ipython/ipython中,但相应的功能似乎尚未包含。(更新)也就是说,Gregory Crosshite的解决方案表明,这是非常容易做到的,即使不调用ipython/nbconvert,所以如果能够正确地连接,这种方法可能是可行的。(然而,将它附加到每个版本控制系统上似乎不是一个好主意——这应该以某种方式连接到笔记本机制中。)
新闻组
- 对版本控制笔记本格式的思考。
问题
- 977:笔记本功能请求(打开)。
- 1280:清除所有保存选项(打开)。(以下是本讨论的内容。)
- 3295:自动导出n
这是我的Git解决方案。它允许您像往常一样添加和提交(和diff):这些操作不会改变您的工作树,同时(重新)运行笔记本也不会改变您的Git历史记录。
虽然这可能适用于其他VCS,但我知道它不满足您的要求(至少VSC不可知性)。不过,它对我来说还是完美的,虽然它没有什么特别出色的地方,而且很多人可能已经使用过它,但我没有找到关于如何通过谷歌搜索来实现它的明确说明。所以它可能对其他人有用。
- 将包含此内容的文件保存在某个位置(对于以下内容,我们假设
~/bin/ipynb_output_filter.py ) - 使其可执行(
chmod +x ~/bin/ipynb_output_filter.py ) 创建文件
~/.gitattributes ,内容如下1*.ipynb filter=dropoutput_ipynb运行以下命令:
1
2
3git config --global core.attributesfile ~/.gitattributes
git config --global filter.dropoutput_ipynb.clean ~/bin/ipynb_output_filter.py
git config --global filter.dropoutput_ipynb.smudge cat- 它只适用于Git
- 在git中,如果您在
somebranch 分支中,而您在git checkout otherbranch; git checkout somebranch 分支中,通常希望工作树不变。在这里,您将丢失笔记本的输出和单元格编号,它们的源代码在两个分支之间有所不同。 - 更一般地说,输出根本没有版本控制,就像格雷戈里的解决方案一样。为了不只是在每次执行任何涉及签出的操作时都将其丢弃,可以通过将其存储在单独的文件中来更改方法(但请注意,在运行上述代码时,提交ID是未知的!),也可能对它们进行版本控制(但请注意,这需要的不仅仅是
git commit notebook_file.ipynb ,尽管它至少可以使git diff notebook_file.ipynb 不受base64垃圾的影响)。 - 也就是说,顺便说一下,如果您确实提取了包含一些输出的代码(即由不使用此方法的其他人提交的代码),那么输出将正常签出。只有本地产生的输出丢失。
如果你按照我的建议采用这个解决方案——也就是说,在全球范围内——你会遇到一些麻烦,以防你想要版本输出的Git回购。因此,如果要禁用特定Git存储库的输出筛选,只需在其中创建一个文件.git/info/attributes,其中
**.ipynb滤波器=
代码现在在自己的git repo中维护。
如果上述说明导致导入错误,请尝试在脚本路径之前添加"ipython":
1git config --global filter.dropoutput_ipynb.clean ipython ~/bin/ipynb_output_filter.py- OnOpType():
- 去掉输出>name.ipynb(
nbstripout ) - 去掉输出>name.clean.ipynb(
nbstripout ,) - 总是从
nbconvert 到python:name.ipynb.py(nbconvert ) - 始终转换为降价:name.ipynb.md(
nbconvert ,ipymd )
- 去掉输出>name.ipynb(
- vcs.configure():
- git difftool,mergetool:nbdiff和nbmerge来自nbdime
nbstripout :从笔记本中去掉输出。- 网址:https://gist.github.com/minrk/6176788
- 来源:https://github.com/kynan/nbstripout
pip install nbstripout; nbstripout install
ipynb_output_filter :从笔记本中去掉输出。- 来源:https://github.com/toobaz/ipynb_output_filter/blob/master/ipynb_output_filter.py
ipymd :转换为jupyter,markdown,o'reilly atlas markdown,opendocument,.py- 网址:https://github.com/rossant/ipymd
nbdime "Jupyter笔记本的区分和合并工具"(2015)- 来源:https://github.com/jupyter/nbdime
- 文档:http://nbdime.readthedocs.io/
nbdiff :以终端友好的方式比较笔记本电脑- nbdime nbdiff作为git diff工具工作:https://nbdime.readthedocs.io/en/latest/git集成快速启动
nbmerge :具有自动冲突解决功能的笔记本电脑的三方合并- nbdime nbmerge用作git合并工具
nbdiff-web :显示笔记本电脑的丰富差异。nbmerge-web :为笔记本提供了一个基于Web的三向合并工具nbshow :以终端友好的方式展示一个笔记本
- 将*.ipynb添加到".hgignore",这样Mercurial就知道它可以忽略这些文件。
- 创建(bash)脚本以启动服务器(使用
--script 选项),并进行版本跟踪 - 保存笔记本会保存
.py 文件,但不会将其签入。- 这是一个缺点:人们可以忘记
- 这也是一个特性:可以保存一个笔记本(稍后继续),而不需要集群存储库历史记录。
- 最好在笔记本仪表板上有一个签到/添加/等按钮
- 结帐(例如)
file@date+rev.py 应该是有帮助的。加上这一点需要做很多工作,也许我会做一次。直到现在,我都是用手做的。
完成!
局限性:
我的解决方案反映了这样一个事实,即我个人不喜欢对生成的东西进行版本控制——请注意,涉及输出的合并几乎可以保证使输出或生产率无效,或者两者都无效。
编辑:
作为内容。显然,以同样的方式,可以做相反的事情:只为特定的存储库启用过滤。
编辑:2016年5月(2017年2月更新):我的脚本有几个备选方案-为了完整性,这里有一个我知道的列表:nbstripout(其他变体),nbstrip,jq。
我们有一个协作项目,产品是Jupyter笔记本,在过去的六个月里我们使用了一种非常有效的方法:我们自动保存
这样,如果有人想查看/下载最新的笔记本,他们可以通过Github或NBviewer进行查看,如果有人想查看笔记本代码是如何更改的,他们只需查看
对于
1 2 3 4 5 6 7 8 9 10 11 | import os from subprocess import check_call def post_save(model, os_path, contents_manager): """post-save hook for converting notebooks to .py scripts""" if model['type'] != 'notebook': return # only do this for notebooks d, fname = os.path.split(os_path) check_call(['jupyter', 'nbconvert', '--to', 'script', fname], cwd=d) c.FileContentsManager.post_save_hook = post_save |
到
如果您不确定要在哪个目录中找到您的
对于
1 2 3 4 5 6 7 8 9 10 11 | import os from subprocess import check_call def post_save(model, os_path, contents_manager): """post-save hook for converting notebooks to .py scripts""" if model['type'] != 'notebook': return # only do this for notebooks d, fname = os.path.split(os_path) check_call(['ipython', 'nbconvert', '--to', 'script', fname], cwd=d) c.FileContentsManager.post_save_hook = post_save |
到
对于
1 | ipython notebook --script |
或者添加行
1 | c.FileNotebookManager.save_script = True |
到
如果您不确定要在哪个目录中找到您的
下面是我们在GitHub上使用这种方法的项目:下面是一个GitHub示例,介绍了最近对笔记本电脑所做的更改。
我们对此非常满意。
我已经创建了基于minrks-gist的
从Pypi那里得到或者简单地
1 | pip install nbstripout |
CyrilleRossant为ipython 3.0提供了一个新的解决方案,它坚持标记文件而不是基于json的ipymd文件:
https://github.com/rossant/ipymd
(2017—02)
策略
工具
如前所述,
1 2 3 4 5 6 7 8 9 10 11 | import os from subprocess import check_call def post_save(model, os_path, contents_manager): """post-save hook for converting notebooks to .py scripts""" if model['type'] != 'notebook': return # only do this for notebooks d, fname = os.path.split(os_path) check_call(['ipython', 'nbconvert', '--to', 'script', fname], cwd=d) c.FileContentsManager.post_save_hook = post_save |
代码取自8009。
我终于找到了一个有效而简单的方法,让朱彼特和吉特一起打得很好。我仍然处在第一步,但我已经认为这比所有其他复杂的解决方案都要好得多。
Visual Studio代码是一个很酷的开源代码编辑器,来自Microsoft。它有一个优秀的python扩展,现在允许您将jupyter笔记本作为python代码导入。
将笔记本导入到python文件后,所有代码和标记都将放在一个普通的python文件中,注释中带有特殊标记。您可以在下图中看到:
python文件只包含笔记本输入单元格的内容。输出将在拆分窗口中生成。你在笔记本里有纯粹的代码,只要你执行它,它就不会改变。没有与代码混合的输出。没有奇怪的JSON不可理解的格式来分析您的差异。
只是纯Python代码,您可以轻松地识别每个差异。
我甚至不再需要修改我的
需要生成一个笔记本来发布或与某人共享?没问题,只需单击交互式Python窗口中的导出按钮。
我刚用了一天,但最后我可以很高兴地使用jupyter和git。
P.S.:vscode代码完成比jupyter好很多。
不幸的是,我对mercurial不太了解,但是我可以为您提供一个可以与git一起使用的解决方案,希望您能够将我的git命令转换为它们的mercurial等价物。
对于后台,在git中,
注意:如果运行该命令会收到一条错误消息,如
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 30 31 32 33 34 35 36 37 | from IPython.nbformat import current import io from os import remove, rename from shutil import copyfile from subprocess import Popen from sys import argv for filename in argv[1:]: # Backup the current file backup_filename = filename +".backup" copyfile(filename,backup_filename) try: # Read in the notebook with io.open(filename,'r',encoding='utf-8') as f: notebook = current.reads(f.read(),format="ipynb") # Strip out all of the output and prompt_number sections for worksheet in notebook["worksheets"]: for cell in worksheet["cells"]: cell.outputs = [] if"prompt_number" in cell: del cell["prompt_number"] # Write the stripped file with io.open(filename, 'w', encoding='utf-8') as f: current.write(notebook,f,format='ipynb') # Run git add to stage the non-output changes print("git add",filename) Popen(["git","add",filename]).wait() finally: # Restore the original file; remove is needed in case # we are running in windows. remove(filename) rename(backup_filename,filename) |
一旦脚本运行在要提交其更改的文件上,只需运行
我使用一种非常务实的方法;它在多个方面对多个笔记本都很有效。它甚至可以让我"转移"笔记本电脑。它既适用于Windows,也适用于Unix/MacOS。
艾尔认为这很简单,就是解决上面的问题…
基本上,不要跟踪
通过使用
这些
我个人使用mercurial对
现在跟踪历史是很简单的;
祝愿
只需遇到"jupytext",这看起来是一个完美的解决方案。它从笔记本生成一个.py文件,然后使两者保持同步。您可以通过.py文件对输入进行版本控制、diff和合并,而不会丢失输出。打开笔记本时,它使用.py作为输入单元格,使用.ipynb作为输出。如果你想把输出包括在git中,那么你可以添加ipynb。
https://github.com/mwouts/jupytext
在深入研究之后,我终于在Jupyter Docs上找到了这个相对简单的预存钩子。它除去单元输出数据。您必须将其粘贴到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def scrub_output_pre_save(model, **kwargs): """scrub output before saving notebooks""" # only run on notebooks if model['type'] != 'notebook': return # only run on nbformat v4 if model['content']['nbformat'] != 4: return for cell in model['content']['cells']: if cell['cell_type'] != 'code': continue cell['outputs'] = [] cell['execution_count'] = None # Added by binaryfunt: if 'collapsed' in cell['metadata']: cell['metadata'].pop('collapsed', 0) c.FileContentsManager.pre_save_hook = scrub_output_pre_save |
来自Rich Signell的答案:
If you aren't sure in which directory to find your
jupyter_notebook_config.py file, you can typejupyter --config-dir [into command prompt/terminal], and if you don't find the file there, you can create it by typingjupyter notebook --generate-config .
我已经构建了解决这个问题的python包
网址:https://github.com/brookisme/gitnb
它为CLI提供了一种受Git启发的语法,用于在Git报告中跟踪/更新/diff笔记本。
这是一个例子
1 2 3 4 5 6 7 8 | # add a notebook to be tracked gitnb add SomeNotebook.ipynb # check the changes before commiting gitnb diff SomeNotebook.ipynb # commit your changes (to your git repo) gitnb commit -am"I fixed a bug" |
请注意,我使用"gitnb commit"的最后一步是提交到您的git repo。它本质上是
1 2 3 4 5 | # get the latest changes from your python notebooks gitnb update # commit your changes ** this time with the native git commit ** git commit -am"I fixed a bug" |
还有更多的方法,可以配置为在每个阶段都需要或多或少的用户输入,但这是一般的想法。
与2019年更好的方法相比,2016年最流行的答案是不一致的黑客攻击。
有几个选项,最好的答案是jupytext。
木星文字
抓住JupyText上的"走向数据科学"文章
它与版本控制的工作方式是将.py和.ipynb文件放在版本控制中。如果需要输入差异,请查看.py;如果需要最新的呈现输出,请查看.ipynb。
值得注意的是:vs studio,nbconvert,nbdime,hydrogen
我认为随着更多的工作,vs studio和/或hydrogen(或类似产品)将成为解决此工作流的主要参与者。
要跟进Pietro Battiston编写的优秀脚本,如果出现这样的Unicode解析错误:
1 2 3 4 5 6 | Traceback (most recent call last): File"/Users/kwisatz/bin/ipynb_output_filter.py", line 33, in <module> write(json_in, sys.stdout, NO_CONVERT) File"/Users/kwisatz/anaconda/lib/python2.7/site-packages/IPython/nbformat/__init__.py", line 161, in write fp.write(s) UnicodeEncodeError: 'ascii' codec can't encode character u'\u2014' in position 11549: ordinal not in range(128) |
您可以在脚本开头添加:
1 2 | reload(sys) sys.setdefaultencoding('utf8') |
由于有如此多的策略和工具来处理笔记本的版本控制,我试图创建一个流程图来选择合适的策略(创建于2019年4月)
好的,因此,根据这里的讨论,当前最好的解决方案是制作一个Git过滤器,在提交时自动从IPynB文件中除去输出。
以下是我所做的工作(从讨论中复制的):
我稍微修改了cfriedline的nbstripout文件,以便在无法导入最新的ipython时提供信息性错误:https://github.com/petered/plato/blob/fb2f4e252f50c79768920d0e47b870a8d799e92b/notebook/config/strip_notebook_输出把它加到我的回购协议中,比如说在
还将文件.gitattributes文件添加到repo的根目录,其中包含:
1 | *.ipynb filter=stripoutput |
创建了一个包含
1 2 3 | git config filter.stripoutput.clean"$(git rev-parse --show-toplevel)/relative/path/to/strip_notebook_output" git config filter.stripoutput.smudge cat git config filter.stripoutput.required true |
运行
我做了阿尔伯特和里奇做的-不要版本.ipynb文件(因为这些文件可能包含图像,这会变得混乱)。相反,要么始终运行
要重新生成笔记本(在签出回购协议或切换分支之后),我将脚本py_file_放在存储笔记本的目录中。
现在,在签出repo之后,只需运行
为了安全起见,还可以添加
编辑:我不再这样做了,因为(a)每次签出分支时,都必须从py文件中重新生成笔记本;(b)笔记本中还有其他东西丢失了,比如记下。我改为使用git过滤器从笔记本中删除输出。关于如何做到这一点的讨论就在这里。
下面的文章中讨论的这个想法怎么样,笔记本的输出应该保存在哪里,理由是生成它可能需要很长时间,而且它很方便,因为Github现在可以渲染笔记本。为导出.py文件添加了自动保存挂钩,用于diff和.html,用于与不使用笔记本或git的团队成员共享。
https://towardsdatascience.com/version-control-for-jupyter-notebook-3e6ceef13392d
这个jupyter扩展允许用户将jupyter笔记本直接推送到github。
请看这里
https://github.com/sat28/githubcommit