将IPython笔记本保持在版本控制下的好策略是什么?

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

allows me to choose between including or excluding output, prevents me from accidentally committing output if I do not want it, allows me to keep output in my local version, allows me to see when I have changes in the inputs using my version control system (i.e. if I only version control the inputs but my local file has outputs, then I would like to be able to see if the inputs have changed (requiring a commit). Using the version control status command will always register a difference since the local file has outputs.) allows me to update my working notebook (which contains the output) from an updated clean notebook. (update)

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

I accidentally commit a version with the the output, thereby polluting my repository. I clear output to use version control, but would really rather keep the output in my local copy (sometimes it takes a while to reproduce for example). Some of the scripts that strip output change the format slightly compared to the Cell/All Output/Clear menu option, thereby creating unwanted noise in the diffs. This is resolved by some of the answers. When pulling changes to a clean version of the file, I need to find some way of incorporating those changes in my working notebook without having to rerun everything. (update)

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

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

更新:我一直在玩我修改过的笔记本版本,它可以选择保存一个.clean版本,每次保存都使用Gregory Crosswhite的建议。这满足了我的大部分约束条件,但留下了以下问题:

This is not yet a standard solution (requires a modification of the ipython source. Is there a way of achieving this behaviour with a simple extension? Needs some sort of on-save hook. A problem I have with the current workflow is pulling changes. These will come in to the .clean file, and then need to be integrated somehow into my working version. (Of course, I can always re-execute the notebook, but this can be a pain, especially if some of the results depend on long calculations, parallel computations, etc.) I do not have a good idea about how to resolve this yet. Perhaps a workflow involving an extension like ipycache might work, but that seems a little too complicated.

笔记

移除(剥离)输出

When the notebook is running, one can use the Cell/All Output/Clear menu option for removing the output. There are some scripts for removing output, such as the script nbstripout.py which remove the output, but does not produce the same output as using the notebook interface. This was eventually included in the ipython/nbconvert repo, but this has been closed stating that the changes are now included in ipython/ipython,but the corresponding functionality seems not to have been included yet. (update) That being said, Gregory Crosswhite's solution shows that this is pretty easy to do, even without invoking ipython/nbconvert, so this approach is probably workable if it can be properly hooked in. (Attaching it to each version control system, however, does not seem like a good idea — this should somehow hook in to the notebook mechanism.)

新闻组

关于版本控制的笔记本格式的思考。

问题

977:笔记本功能请求(打开)。 1280:清除-all保存选项(打开)。(从下面的讨论。) 3295:自动导出的笔记本:只导出显式标记的单元格(关闭)。扩展解决11添加写和执行魔法(合并)。

把请求

1621: clear In[] prompt numbers on "Clear All Output" (Merged). (See also 2519 (Merged).) 1563: clear_output improvements (Merged). 3065: diff-ability of notebooks (Closed). 3291: Add the option to skip output cells when saving. (Closed). This seems extremely relevant, however was closed with the suggestion to use a "clean/smudge" filter. A relevant question what can you use if you want to strip off output before running git diff? seems not to have been answered. 3312: WIP: Notebook save hooks (Closed). 3747: ipynb -> ipynb transformer (Closed). This is rebased in 4175. 4175: nbconvert: Jinjaless exporter base (Merged). 142: Use STDIN in nbstripout if no input is given (Open).


当前回答

不幸的是,我对Mercurial了解不多,但我可以为您提供一个与Git一起工作的可能解决方案,希望您能够将我的Git命令转换为对应的Mercurial命令。

对于后台,在Git中,add命令将对文件所做的更改存储到暂存区域。完成此操作后,Git将忽略对文件的任何后续更改,除非您告诉它也执行这些更改。因此,下面的脚本,对于每个给定的文件,剥离所有的输出和prompt_number部分,运行剥离的文件,然后恢复原始文件:

注意:如果运行此命令会得到类似ImportError的错误消息:没有名为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。

其他回答

你可以使用这个jupyter扩展。它可以让你直接上传你的ipython笔记本到github。

https://github.com/sat28/githubcommit

我还制作了一个视频来演示这些步骤 youtube链接

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

从PyPI或简单地获得它

pip install nbstripout

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

https://github.com/mwouts/jupytext

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

虽然这可能适用于其他VSC,但我知道它不能满足您的要求(至少VSC不可知)。不过,它对我来说是完美的,尽管它没有什么特别出色的地方,而且很多人可能已经在使用它了,但我没有通过谷歌找到关于如何实现它的明确说明。所以它可能对其他人有用。

将包含此内容的文件保存在某个位置(对于下面的内容,让我们假设~/bin/ipynb_output_filter.py) (chmod +x ~/bin/ipynb_output_filter.py) 创建文件~/。Gitattributes,包含以下内容 *。ipynb过滤器= dropoutput_ipynb 执行如下命令: Git配置——全局核心。attributesfile ~ / .gitattributes Git配置——global filter.dropoutput_ipynb。干净的~ / bin / ipynb_output_filter.py Git配置——global filter.dropoutput_ipynb。涂抹的猫

完成了!

限制:

it works only with git in git, if you are in branch somebranch and you do git checkout otherbranch; git checkout somebranch, you usually expect the working tree to be unchanged. Here instead you will have lost the output and cells numbering of notebooks whose source differs between the two branches. more in general, the output is not versioned at all, as with Gregory's solution. In order to not just throw it away every time you do anything involving a checkout, the approach could be changed by storing it in separate files (but notice that at the time the above code is run, the commit id is not known!), and possibly versioning them (but notice this would require something more than a git commit notebook_file.ipynb, although it would at least keep git diff notebook_file.ipynb free from base64 garbage). that said, incidentally if you do pull code (i.e. committed by someone else not using this approach) which contains some output, the output is checked out normally. Only the locally produced output is lost.

我的解决方案反映了这样一个事实:我个人不喜欢对生成的东西进行版本控制——注意,涉及输出的合并几乎肯定会使输出或您的生产力无效,或者两者都无效。

编辑:

如果你确实采用了我建议的解决方案——也就是全局的解决方案——你会遇到一些麻烦,比如你想要版本输出的git repo。因此,如果你想禁用特定git存储库的输出过滤,只需在其中创建一个文件.git/info/attributes,使用 **ipynb过滤器=

作为内容。显然,以同样的方式也可以做相反的事情:仅为特定的存储库启用筛选。

代码现在在它自己的git repo中维护 如果上面的指令导致ImportErrors,尝试在脚本路径之前添加"ipython": Git配置——global filter.dropoutput_ipynb。清除ipython ~/bin/ipynb_output_filter.py

编辑:2016年5月(更新于2017年2月):我的脚本有几个替代方案-为了完整性,这里是我知道的列表:nbstripout(其他变体),nbstrip, jq。

不幸的是,我对Mercurial了解不多,但我可以为您提供一个与Git一起工作的可能解决方案,希望您能够将我的Git命令转换为对应的Mercurial命令。

对于后台,在Git中,add命令将对文件所做的更改存储到暂存区域。完成此操作后,Git将忽略对文件的任何后续更改,除非您告诉它也执行这些更改。因此,下面的脚本,对于每个给定的文件,剥离所有的输出和prompt_number部分,运行剥离的文件,然后恢复原始文件:

注意:如果运行此命令会得到类似ImportError的错误消息:没有名为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。