在版本控制下使用IPython笔记本

问题描述 投票:523回答:19

IPython笔记本电脑置于版本控制之下的好策略是什么?

笔记本格式非常适合版本控制:如果想要版本控制笔记本和输出,那么这非常有效。当人们只想对输入进行版本控制时,就会产生烦恼,不包括可能是大型二进制blob的单元格输出(也就是“构建产品”),特别是对于电影和情节。特别是,我试图找到一个良好的工作流程:

  • 允许我选择包括或排除输出,
  • 如果我不想要它会阻止我意外地提交输出,
  • 允许我保持我的本地版本的输出,
  • 允许我看看当我使用我的版本控制系统更改输入时(即如果我只对版本控制输入但我的本地文件有输出,那么我希望能够看到输入是否已更改(需要提交) )。使用版本控制状态命令将始终注册差异,因为本地文件有输出。)
  • 允许我从更新的干净笔记本更新我的工作笔记本(包含输出)。 (更新)

如上所述,如果我选择包含输出(例如,当使用nbviewer时这是可取的),那么一切都很好。问题是当我不想版本控制输出时。有一些工具和脚本可以剥离笔记本的输出,但我经常会遇到以下问题:

  1. 我不小心提交了一个带有输出的版本,从而污染了我的存储库。
  2. 我清除输出以使用版本控制,但实际上宁愿将输出保留在我的本地副本中(例如,有时需要一段时间来重现)。
  3. Cell/All Output/Clear菜单选项相比,剥离输出的一些脚本会稍微改变格式,从而在差异中产生不必要的噪声。这可以通过一些答案来解决。
  4. 当将更改提取到文件的干净版本时,我需要找到一些方法将这些更改合并到我的工作笔记本中而无需重新运行所有内容。 (更新)

我已经考虑过几个选项,我将在下面讨论,但还没有找到一个很好的综合解决方案。完整的解决方案可能需要对IPython进行一些更改,或者可能依赖于一些简单的外部脚本。我目前使用mercurial,但想要一个也适用于git的解决方案:理想的解决方案是版本控制不可知。

这个问题已经多次讨论过,但从用户的角度来看,没有明确或明确的解决方案。这个问题的答案应该提供明确的策略。如果它需要最近(甚至开发)版本的IPython或一个易于安装的扩展,这很好。

更新:我一直在玩my modified notebook版本,可选择使用.clean保存Gregory Crosswhite's suggestions版本。这满足了我的大多数约束,但是仍然没有解决以下问题:

  1. 这还不是一个标准的解决方案(需要修改ipython源。有没有办法通过简单的扩展来实现这种行为?需要某种on-save钩子。
  2. 我对当前工作流程的一个问题是拉动变化。这些将进入.clean文件,然后需要以某种方式集成到我的工作版本中。 (当然,我总是可以重新执行笔记本,但这可能会很痛苦,特别是如果某些结果取决于长时间的计算,并行计算等)。我还不知道如何解决这个问题。也许涉及像ipycache这样的扩展的工作流程可能会起作用,但这似乎有点过于复杂。

Notes

删除(剥离)输出

  • 笔记本电脑运行时,可以使用Cell/All Output/Clear菜单选项删除输出。
  • 有一些用于删除输出的脚本,例如删除输出的脚本nbstripout.py,但不会产生与使用笔记本界面相同的输出。这最终被包含在ipython/nbconvert回购中,但是已经关闭,说明这些变化现在包含在ipython/ipython中,但相应的功能似乎还没有被包括在内。 (更新)话虽如此,Gregory Crosswhite's solution表明这很容易做到,即使没有调用ipython/nbconvert,所以如果可以正确地连接它,这种方法可能是可行的。(然而,将它附加到每个版本控制系统似乎不相同好像一个好主意 - 这应该以某种方式挂钩到笔记本机制。)

新闻组

问题

请求

version-control ipython jupyter-notebook
19个回答
115
投票

这是我用git的解决方案。它允许您像往常一样添加和提交(和差异):这些操作不会改变您的工作树,同时(重新)运行笔记本不会改变您的git历史记录。

虽然这可能适用于其他VCS,但我知道它不能满足您的要求(至少VSC不可知)。尽管如此,它对我来说仍然是完美的,虽然没有什么特别精彩,很多人可能已经使用过它,但我没有找到关于如何通过Google搜索来实现它的明确指示。所以它可能对其他人有用。

  1. this content保存一个文件(对于以下内容,让我们假设~/bin/ipynb_output_filter.py
  2. 让它可执行(chmod +x ~/bin/ipynb_output_filter.py
  3. 使用以下内容创建文件~/.gitattributes *.ipynb filter=dropoutput_ipynb
  4. 运行以下命令: git 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,你通常希望工作树不变。相反,您将丢失两个分支之间源不同的笔记本的输出和单元格编号。
  • 更一般地说,输出完全没有版本化,就像Gregory的解决方案一样。为了不是每次你做任何涉及结账的事情都扔掉它,可以通过将它存储在单独的文件中来改变方法(但请注意,在运行上面的代码时,提交id是未知的!),并且可能对它们进行版本控制(但请注意,这需要的不仅仅是git commit notebook_file.ipynb,尽管它至少可以使git diff notebook_file.ipynb免于base64垃圾)。
  • 也就是说,顺便说一句,如果您确实提取包含某些输出的代码(即由不使用此方法的其他人提交),则输出会正常检出。只丢失本地产生的输出。

我的解决方案反映了这样一个事实,即我个人不喜欢将生成的内容保留为版本 - 请注意,涉及输出的合并几乎可以保证输出或生产力无效或两者兼而有之。

编辑:

  • 如果您按照我的建议采用了解决方案 - 也就是说,全局 - 您将遇到麻烦,以防某些git repo您希望版本输出。因此,如果要禁用特定git存储库的输出过滤,只需在其中创建一个.git / info / attributes文件, **。ipynb filter =

作为内容。显然,以相同的方式可以执行相反的操作:仅针对特定存储库启用过滤。

  • 代码现在保存在自己的git repo
  • 如果上述指令导致ImportErrors,请尝试在脚本路径前添加“ipython”: git config --global filter.dropoutput_ipynb.clean ipython ~/bin/ipynb_output_filter.py

编辑:2016年5月(2017年2月更新):我的脚本有几种替代方案 - 为了完整性,这里列出了我所知道的:nbstripoutother variants),nbstripjq


4
投票

刚刚遇到“jupytext”,看起来像一个完美的解决方案。它从笔记本生成一个.py文件,然后保持同步。您可以通过.py文件对版本控制,差异和合并输入进行版本控制,而不会丢失输出。当您打开笔记本时,它使用.py作为输入单元格,使用.ipynb作为输出。如果你想在git中包含输出,那么你可以添加ipynb。

https://github.com/mwouts/jupytext


3
投票

要跟进Pietro Battiston的优秀脚本,如果你得到这样的Unicode解析错误:

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)

您可以在脚本的开头添加:

reload(sys)
sys.setdefaultencoding('utf8')

3
投票

我已经构建了python包来解决这个问题

https://github.com/brookisme/gitnb

它提供了一个带有git启发语法的CLI,用于在git仓库中跟踪/更新/区分笔记本。

继承人就是一个例子

# 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。它本质上是一个包装

# 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"

还有几种方法,可以进行配置,以便在每个阶段需要更多或更少的用户输入,但这是一般的想法。


3
投票

在挖掘之后,我终于找到了this relatively simple pre-save hook on the Jupyter docs。它剥离单元输出数据。您必须将其粘贴到jupyter_notebook_config.py文件中(有关说明,请参阅下文)。

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's answer

如果您不确定在哪个目录中找到jupyter_notebook_config.py文件,可以键入jupyter --config-dir [进入命令提示符/终端],如果在那里找不到该文件,可以通过输入jupyter notebook --generate-config来创建它。


3
投票

与2019年更好的方法相比,上面非常受欢迎的2016年答案是不一致的黑客。

存在几种选择,回答问题的最好的选择是Jupytext。

Jupytext

抓住Towards Data Science article on Jupytext

它与版本控制一起使用的方式是将.py和.ipynb文件放在版本控制中。如果你想要输入差异,请查看.py,如果你想要最新的渲染输出,请查看.ipynb。

值得注意的提到:VS工作室,nbconvert,nbdime,氢气

我认为通过更多的工作,VS工作室和/或氢气(或类似的)将成为该工作流程解决方案的主要参与者。


2
投票

我做了Albert&Rich所做的事情 - 不要版本.ipynb文件(因为这些文件可能包含混乱的图像)。相反,要么总是运行ipython notebook --script或将c.FileNotebookManager.save_script = True放在配置文件中,以便在保存笔记本时始终创建(可版本化的).py文件。

要重新生成笔记本(在签出仓库或切换分支后),我将脚本py_file_to_notebooks.py放在我存储笔记本的目录中。

现在,在检出一个仓库后,只需运行python py_file_to_notebooks.py即可生成ipynb文件。切换分支后,您可能必须运行python py_file_to_notebooks.py -ov来覆盖现有的ipynb文件。

为了安全起见,还可以将*.ipynb添加到您的.gitignore文件中。

编辑:我不再这样做了,因为(A)你每次检查一个分支时都必须从py文件中重新生成你的笔记本,而且(B)还有其他东西,比如你输掉的笔记本中的降价。我改为使用git过滤器从笔记本中删除输出。关于如何做到这一点的讨论是here


2
投票

好的,所以看起来当前最好的解决方案,根据讨论here,是使git过滤器在提交时自动剥离ipynb文件的输出。

以下是我为使其工作所做的工作(从该讨论中复制):

当你无法导入最新的IPython时,我稍微修改了cfriedline的nbstripout文件以提供信息错误:https://github.com/petered/plato/blob/fb2f4e252f50c79768920d0e47b870a8d799e92b/notebooks/config/strip_notebook_output并将其添加到我的repo中,让我们说在./relative/path/to/strip_notebook_output

还将文件.gitattributes文件添加到repo的根目录,其中包含:

*.ipynb filter=stripoutput

并创建了一个包含setup_git_filters.sh

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

然后跑source setup_git_filters.sh。花哨的$(git rev-parse ...)就是在任何(Unix)机器上找到你的repo的本地路径。


2
投票

由于存在很多策略和工具来处理笔记本的版本控制,我试图创建一个流程图来选择合适的策略(创建于2019年4月)

Decision flow to pick version control strategy


0
投票

在下面的帖子中讨论的想法如何,应该保留笔记本的输出,并且可能需要很长时间来生成它,并且它很方便,因为GitHub现在可以渲染笔记本。为导出.py文件添加了自动保存挂钩,用于差异和.html与不使用笔记本或git的团队成员共享。

https://towardsdatascience.com/version-control-for-jupyter-notebook-3e6cef13392d


0
投票

这个jupyter扩展使用户可以将jupyter笔记本直接推送到github。

请看这里

https://github.com/sat28/githubcommit


56
投票

我们有一个合作项目,产品是Jupyter笔记本,我们在过去的六个月中使用了一种方法很好:我们激活自动保存.py文件并跟踪.ipynb文件和.py文件。

这样,如果有人想查看/下载最新的笔记本,他们可以通过github或nbviewer这样做,如果有人想看看笔记本代码是如何变化的,他们可以看看.py文件的变化。

对于Jupyter笔记本服务器,可以通过添加行来实现

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

jupyter_notebook_config.py文件并重新启动笔记本服务器。

如果您不确定在哪个目录中找到jupyter_notebook_config.py文件,可以输入jupyter --config-dir,如果在那里找不到该文件,可以通过输入jupyter notebook --generate-config来创建它。

对于Ipython 3笔记本服务器,可以通过添加行来实现

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

ipython_notebook_config.py文件并重新启动笔记本服务器。这些行来自github问题答案@minrk provided和@dror也包括他们的SO答案。

对于Ipython 2笔记本服务器,可以通过使用以下命令启动服务器来完成:

ipython notebook --script

或者添加该行

c.FileNotebookManager.save_script = True

ipython_notebook_config.py文件并重新启动笔记本服务器。

如果您不确定在哪个目录中找到ipython_notebook_config.py文件,可以输入ipython locate profile default,如果在那里找不到该文件,可以通过输入ipython profile create来创建它。

这是our project on github that is using this approach:这是一个github example of exploring recent changes to a notebook

我们对此非常满意。


35
投票

我创建了基于nbstripoutMinRKs gist,它支持Git和Mercurial(感谢mforbes)。它既可以在命令行中单独使用,也可以作为过滤器使用,可以通过nbstripout install / nbstripout uninstall轻松(非)安装在当前存储库中。

PyPI或简单地获取它

pip install nbstripout

13
投票

这是Cyrille Rossant针对IPython 3.0的新解决方案,它坚持使用markdown文件而不是基于json的ipymd文件:

https://github.com/rossant/ipymd


11
投票

(2017-02)

策略

  • on_commit(): 剥离输出> name.ipynb(nbstripout,) 剥离输出> name.clean.ipynb(nbstripout,) 总是nbconvert到python:name.ipynb.py(nbconvert) 总是转换为markdown:name.ipynb.md(nbconvertipymd
  • vcs.configure(): git difftool,mergetool:来自nbdime的nbdiff和nbmerge

工具


8
投票

正如所指出的那样,--script3.x被弃用。可以通过应用post-save-hook来使用此方法。特别是,将以下内容添加到ipython_notebook_config.py

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


7
投票

我终于找到了一种高效而简单的方法让Jupyter和Git很好地融合在一起。我还在迈出第一步,但我已经认为它比其他所有复杂的解决方案都要好得多。

Visual Studio Code是一款来自微软的酷炫开源代码编辑器。它有一个很好的Python扩展,现在允许你将import a Jupyter Notebook作为python代码。

将笔记本导入python文件后,所有代码和markdown将一起放在普通的python文件中,注释中带有特殊标记。您可以在下图中看到:

VSCode editor with a notebook converted to python

你的python文件只包含笔记本输入单元格的内容。输出将在拆分窗口中生成。你在笔记本中有纯粹的代码,当你执行它时它不会改变。没有与您的代码混合输出。没有奇怪的Json难以理解的格式来分析你的差异。

只需纯Python代码,您可以轻松识别每个差异。

我甚至不需要再版我的.ipynb文件了。我可以在*.ipynb放一条.gitignore线。

需要生成笔记本才能发布或与他人分享?没问题,只需在交互式python窗口中使用click the export button

Exporting a python file to Notebook format

我一直在使用它只有一天,但最后我可以愉快地使用Jupyter与Git。

P.S。:VSCode代码完成比Jupyter好很多。


6
投票

我用一种非常务实的方法;这对于几个笔记本电脑来说很有效。它甚至可以让我“转移”笔记本电脑。它适用于Windows作为Unix / MacOS。 Al认为很简单,就是解决上面的问题......

Concept

基本上,不要跟踪.ipnyb文件,只跟踪相应的.py文件。 通过使用--script选项启动笔记本电脑 - 服务器,保存笔记本时会自动创建/保存该文件。

那些.py文件确实包含所有输入;非代码保存到注释中,单元格边框也是如此。可以将这些文件读取/导入(并拖动)到笔记本服务器中以(重新)创建笔记本。只有输出消失了;直到重新运行。

我个人使用mercurial版本跟踪.py文件;并使用普通(命令行)命令添加,签入(ect)。大多数其他(D)VCS将允许这样做。

现在很容易跟踪历史; .py很小,文字和简单的差异。有一段时间,我们需要一个克隆(只是分支;在那里启动第二个笔记本 - 服务器),或者旧版本(检出并导入到笔记本服务器中)等。

Tips & tricks

  • 将* .ipynb添加到'.hgignore',因此Mercurial知道它可以忽略这些文件
  • 创建一个(bash)脚本来启动服务器(使用--script选项)并对其进行版本跟踪
  • 保存笔记本会保存.py文件,但不会将其签入。 这是一个缺点:人们可以忘记这一点 它也是一个特性:可以保存笔记本(并在以后继续),而无需集群存储库历史记录。

Wishes

  • 在笔记本电脑仪表板上有一个用于登记/添加/等的按钮会很不错
  • 结账(例如)file@date+rev.py)应该是有用的。添加它会有很多工作;也许我会这样做一次。到现在为止,我只是手工完成。

6
投票

不幸的是,我对Mercurial了解不多,但我可以给你一个与Git一起使用的可能解决方案,希望你能将我的Git命令转换成他们的Mercurial等价物。

对于后台,在Git中,add命令将对文件所做的更改存储到暂存区域。完成此操作后,Git将忽略对该文件的任何后续更改,除非您告诉它也将其暂存。因此,下面的脚本,对于每个给定的文件,删除所有outputsprompt_number sections,分阶段剥离文件,然后恢复原始:

注意:如果运行它会得到像ImportError: No module named IPython.nbformat这样的错误消息,那么使用ipython来运行脚本而不是python

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)

一旦脚本在您想要提交更改的文件上运行,只需运行git commit

© www.soinside.com 2019 - 2024. All rights reserved.