在版本控制下使用 IPython / Jupyter Notebooks

Posted

技术标签:

【中文标题】在版本控制下使用 IPython / Jupyter Notebooks【英文标题】:Using IPython / Jupyter Notebooks Under Version Control 【发布时间】:2013-09-15 02:19:37 【问题描述】:

让IPython 笔记本处于版本控制之下的好策略是什么?

notebook 格式非常适合版本控制:如果想要对 notebook 和输出进行版本控制,那么这很有效。当一个人只想对输入进行版本控制时,麻烦就来了,不包括可能是大二进制 blob 的单元输出(又名“构建产品”),尤其是对于电影和情节。特别是,我正在努力寻找一个好的工作流程:

允许我在包含或排除输出之间进行选择, 防止我在不需要时意外提交输出, 允许我在本地版本中保留输出, 允许我使用我的版本控制系统查看输入何时发生更改(即,如果我只对输入进行版本控制但我的本地文件有输出,那么我希望能够查看输入是否已更改(需要提交)。使用版本控制状态命令将始终记录差异,因为本地文件有输出。) 允许我从更新的干净笔记本更新我的工作笔记本(包含输出)。 (更新)

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

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

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

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

更新:我一直在玩my modified notebook 版本,它可以选择在每次保存时使用Gregory Crosswhite's suggestions 保存.clean 版本。这满足了我的大部分限制,但未解决以下问题:

    这还不是标准解决方案(需要修改 ipython 源代码。有没有办法通过简单的扩展来实现此行为?需要某种 on-save 挂钩。 我在当前工作流程中遇到的一个问题是拉取更改。这些将进入.clean 文件,然后需要以某种方式集成到我的工作版本中。 (当然,我总是可以重新执行笔记本,但这可能会很痛苦,尤其是如果某些结果依赖于长计算、并行计算等。)我还不知道如何解决这个问题.也许涉及ipycache 之类的扩展的工作流可能会起作用,但这似乎有点太复杂了。

注意事项

移除(剥离)输出

当笔记本运行时,可以使用Cell/All Output/Clear 菜单选项来删除输出。 有一些用于删除输出的脚本,例如脚本nbstripout.py 会删除输出,但不会产生与使用笔记本界面相同的输出。这最终包含在 ipython/nbconvert 存储库中,但已关闭说明更改现在包含在 ipython/ipython 中,但似乎尚未包含相应的功能。 (更新) 话虽如此,Gregory Crosswhite's solution 表明这很容易做到,即使不调用 ipython/nbconvert,所以如果可以正确挂钩,这种方法可能是可行的。(附加它然而,对于每个版本控制系统来说,这似乎不是一个好主意——这应该以某种方式与笔记本机制挂钩。)

新闻组

Thoughts on the notebook format for version control。

问题

977: Notebook feature requests (Open)。 1280: Clear-all on save option (Open)。 (来自this discussion。) 3295: autoexported notebooks: only export explicitly marked cells (Closed)。已通过扩展 11 Add writeandexecute magic (Merged) 解决。

拉取请求

1621: clear In[] prompt numbers on "Clear All Output" (Merged)。 (另见2519 (Merged)。) 1563: clear_output improvements (Merged)。 3065: diff-ability of notebooks (Closed)。 3291: Add the option to skip output cells when saving. (Closed)。这似乎非常相关,但是由于建议使用“清洁/涂抹”过滤器而被关闭。一个相关的问题what can you use if you want to strip off output before running git diff? 似乎没有得到回答。 3312: WIP: Notebook save hooks (Closed). 3747: ipynb -> ipynb transformer (Closed)。这是在 4175 中重新设置的。 4175: nbconvert: Jinjaless exporter base (Merged)。 142: Use STDIN in nbstripout if no input is given (Open)。

【问题讨论】:

一旦你有一个用于删除输出的工作脚本,你可以在提交之前使用一个 Git “clean”过滤器自动应用它(参见 clean/smudge filters)。 @foobarbecue 该问题包含不令人满意的解决方法:每个问题都有至少一个限制。现在 PR 4175 已经合并了,大概可以制定一个完整的解决方案,但这仍然需要做。只要我有时间,如果其他人在此期间没有提供令人满意的解决方案,我会做(作为答案)。 @saroele 我还没有找到推荐的解决方案:我打算使用--script 选项,但它已被删除。我正在等到保存后挂钩 (which are planned) 实施后,我认为我将能够提供一个结合多种技术的可接受的解决方案。 @mforbes 看起来 PR 在您发表评论几天后才被合并。您或比我更有知识的人能否在这里发布一个答案,说明如何使用新功能? @kobejohn:我刚刚添加了一个答案 【参考方案1】:

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

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

    在某处使用this content 保存文件(对于以下内容,我们假设~/bin/ipynb_output_filter.py

    使其可执行 (chmod +x ~/bin/ipynb_output_filter.py)

    创建文件~/.gitattributes,内容如下

    *.ipynb 过滤器=dropoutput_ipynb

    运行以下命令:

    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 过滤器=

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

代码现在维护在自己的git repo

如果上述说明导致 ImportErrors,请尝试在脚本路径前添加“ipython”:

  git config --global filter.dropoutput_ipynb.clean ipython ~/bin/ipynb_output_filter.py

编辑:2016 年 5 月(2017 年 2 月更新):我的脚本有几个替代方案 - 为了完整起见,这里列出了我知道的那些:nbstripout (other variants )、nbstrip、jq。

【讨论】:

您如何处理合并您提取的更改的问题?您是否只需要重新生成所有输出? (我认为这是您的第二个限制的表现。) @zhermes:这个扩展版应该没问题 有没有办法将此 git 过滤器方法与外部差异工具一起使用?如果我使用普通命令行工具,则会应用过滤器,但如果我使用 meld 作为差异工具,则不会应用。 ***.com/q/30329615/578770 为了避免得到ImportError,我不得不对上面的内容进行更改以使用ipython运行:git config --global filter.dropoutput_ipynb.clean ipython ~/bin/ipynb_output_filter.py 很棒的解决方案 Pietro,谢谢 :) 在我的案例中使用您的脚本时,我更改了 2 件事:1) 我更喜欢在回购根目录中的 .gitattributes 中声明过滤器,而不是 ~/.gitattributes,英石其他人有和我一样的过滤器 2)我将正则表达式定义为workdir/**/*.ipynb filter=dropoutput_ipynb,我把我的大部分笔记本放在 workdir/ => 如果我仍然想用输出推送一个笔记本并享受 github 中的可书签渲染,我只是把它放在那个文件夹之外。【参考方案2】:

我们有一个合作项目,产品是 Jupyter Notebooks,在过去的六个月里,我们使用了一种效果很好的方法:我们自动激活保存 .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。

我们对此非常满意。

【讨论】:

感谢补充证据表明使用 --script 在实践中有效。这样做的问题是,如果保留图像,实际的笔记本可能会很大。沿这种方式的理想解决方案可能会使用 git-annex 之类的东西来仅跟踪最新的完整笔记本。 在 Ipython 3.x 中,--script 已被弃用。 ipython.org/ipython-doc/3/whatsnew/version3.html 更新: 由于 iPython 中 Jupyter 的“大分裂”,此解决方案在 iPython 版本 4 中被破坏。要将此解决方案调整为版本 4,请使用命令 jupyter notebook --generate-config 创建配置文件。命令jupyter --config-dir 找出哪个目录包含配置文件。并且@Rich给出的代码sn-p应该添加到名为jupyter_notebook_config.py的文件中。其余的和以前一样工作。 除了@mobiusdumpling 的要点,将check_call(['ipython' 替换为check_call(['jupyter',否则您将收到警告ipython nbconvert 已弃用,您应该改用jupyter nbconvert。 (Jupyter v4.1.0, iPython v4.1.2) 如果要将.py 文件保存到当前目录以外的其他目录,请将'--output-dir', 'your_dir' 添加到check_call。例如,check_call(['jupyter', 'nbconvert', '--to', 'script', fname, '--output-dir', './src'], cwd=d)【参考方案3】:

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

从PyPI 或直接获取它

pip install nbstripout

【讨论】:

我正在考虑使用上述保存后挂钩自动创建的 .ipynb 和相应的 .py 的工作流程。我想将 .py 用于差异 - nbstripout 是否能够从单元执行计数器中清除 .py 文件(# In[1] 更改为 In[*]),这样它们就不会弄乱差异或者我应该为此创建一个简单的脚本? @KrzysztofSłowiński 不,nbstripout 不容易支持此用例,因为它依赖于 Notebook 的 JSON 格式。您最好编写一个专门针对您的用例的脚本。 nbstripout 是否可以选择在给定文件夹上递归工作(我说的是可执行文件本身)? 不是直接的,也不需要。您可以简单地使用find 或其他一些递归查找要操作的文件的标准方法。【参考方案4】:

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

【讨论】:

【参考方案5】:

与 2019 年更好的方法相比,上面非常流行的 2016 年答案是不一致的黑客攻击。

存在多种选择,最能回答问题的是 Jupytext。

Jupytext

赶上Towards Data Science article on Jupytext

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

值得注意的提及:VS studio、nbconvert、nbdime、hydro

我认为通过更多的工作,VS studio 和/或hydrogen(或类似的)将成为此工作流程解决方案中的主导者。

【讨论】:

【参考方案6】:

在删除笔记本中的输出几年后,我试图提出一个更好的解决方案。我现在使用 Jupytext,这是我为 Jupyter Notebook 和 Jupyter Lab 设计的扩展。

Jupytext 可以将 Jupyter 笔记本转换为各种文本格式(Scripts、Markdown 和 R Markdown)。反之亦然。它还提供将笔记本配对与其中一种格式的选项,并自动同步笔记本的两种表示形式(.ipynb.md/.py/.R 文件)。

让我解释一下 Jupytext 是如何回答上述问题的:

允许我在包含或排除输出之间进行选择,

.md/.py/.R 文件仅包含输入单元格。您应该始终跟踪此文件。仅当您想跟踪输出时才对 .ipynb 文件进行版本控制。

防止我在不需要时意外提交输出,

*.ipynb 添加到.gitignore

允许我将输出保留在本地版本中,

输出保存在(本地).ipynb 文件中

允许我使用我的版本控制系统查看输入何时发生更改(即,如果我只对输入进行版本控制但我的本地文件有输出,那么我希望能够查看输入是否已更改(需要提交)。使用版本控制状态命令将始终记录差异,因为本地文件有输出。)

.py/.R.md 文件上的差异是您要查找的内容

允许我从更新的干净笔记本更新我的工作笔记本(包含输出)。 (更新)

拉取.py/.R.md 文件的最新版本并在 Jupyter (Ctrl+R) 中刷新您的笔记本。您将从文本文件中获得最新的输入单元格,以及来自.ipynb 文件的匹配输出。内核不受影响,这意味着您的局部变量被保留 - 您可以继续从离开它的地方工作。

我喜欢 Jupytext 的原因是可以在您喜欢的 IDE 中编辑笔记本(以 .py/.R.md 文件的形式)。使用这种方法,重构笔记本变得容易。完成后,您只需在 Jupyter 中刷新笔记本即可。

如果您想尝试一下:使用 pip install jupytext 安装 Jupytext,然后重新启动您的 Jupyter Notebook 或 Lab 编辑器。打开您要进行版本控制的笔记本,并使用 Jupyter 笔记本中的 Jupytext Menu(或 Jupyter 实验室中的 Jupytext commands)将其配对到 Markdown 文件(或脚本)。保存你的笔记本,你会得到两个文件:原始的.ipynb,加上承诺的笔记本文本表示,非常适合版本控制!

对于那些可能感兴趣的人:Jupytext 也可以在command line 上获得。

【讨论】:

【参考方案7】:

更新:现在你可以edit Jupyter Notebook 文件直接在 Visual Studio Code 中。您可以选择编辑 笔记本或转换后的python文件。

我终于找到了一种高效且简单的方法,可以让 Jupyter 和 Git 很好地协同工作。我仍处于第一步,但我已经认为它比所有其他复杂的解决方案要好得多.

Visual Studio Code 是来自 Microsoft 的一款酷炫的开源代码编辑器。它有一个出色的 Python 扩展,现在允许您将 import a Jupyter Notebook 作为 Python 代码。现在您也可以直接edit Jupyter Notebooks。

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

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

只是纯 Python 代码,您可以在其中轻松识别每个差异。

我什至不再需要对我的.ipynb 文件进行版本控制。我可以在.gitignore 中添加*.ipynb 行。

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

如果您直接编辑笔记本,现在有一个图标Convert and save to a python script

这是 Visual Studio Code 中笔记本的屏幕截图:

我只用了一天,但终于可以愉快地将 Jupyter 与 Git 一起使用。

P.S.:VSCode 代码补全比 Jupyter 好很多。

【讨论】:

你知道这是如何导出为 pdf 的吗?它使用的实际命令是什么?使用 vscode 时,我可以转换为 pdf 并保留 matplotlib 图。但是,当使用 jupyterlab 时,生成的 pdf 不会保留任何输出。理想情况下,我想使用 jupytext 生成没有代码但输出的 pdf @bryce,我不知道。但是看看纯 Jupyter 而不是 JupyterLab。我认为它的导出功能效果更好。【参考方案8】:

(2017-02)

策略

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

工具

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-integration-quickstart nbmerge:笔记本三路合并,自动解决冲突 nbdime nbmerge 用作 git 合并工具 nbdiff-web: 向您展示笔记本的丰富渲染差异 nbmerge-web:给你一个基于网络的笔记本三向合并工具 nbshow:以终端友好的方式呈现单个笔记本

【讨论】:

【参考方案9】:

这是 Cyrille Rossant 为 IPython 3.0 提供的一个新解决方案,它保留在 markdown 文件而不是基于 json 的 ipymd 文件:

https://github.com/rossant/ipymd

【讨论】:

似乎还不支持 Jupyter。 我正在将 ipymd 与最新的 Jupyter 一起成功使用——您是否收到任何具体问题或错误消息?【参考方案10】:

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

https://github.com/mwouts/jupytext

【讨论】:

【参考方案11】:

正如所指出的,--script3.x 中已弃用。可以通过应用保存后挂钩来使用此方法。特别是,将以下内容添加到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。

【讨论】:

感谢您演示如何使用保存后挂钩。不幸的是,正如前面提到的,从.py 文件返回到笔记本是有问题的,所以很遗憾这不是一个完整的解决方案。 (我有点希望这是因为区分 .py 文件而不是笔记本非常好。也许新的 notebook diff 功能会很有用。 谢谢!我现在正在使用这个技巧来重现 --script 行为,而不管版本控制如何。起初我遇到了一些问题,所以以防万一我可以节省一些时间:1)如果配置文件文件夹中缺少ipython_notebook_config.py,请运行ipython profile create 来生成它。 2) 如果似乎忽略了保存后挂钩,请使用--debug 运行 ipython 来诊断问题。 3) 如果脚本失败并出现错误ImportError: No module named mistune - 简单安装 minstue: pip install mistune.【参考方案12】:

很遗憾,我对 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

【讨论】:

感谢您的建议。 Mercurial 并没有像 git 这样的临时区域(尽管为此目的可以使用 mercurial queues)。同时,我尝试将此代码添加到保存挂钩中,该挂钩保存带有.clean 扩展名的干净版本。不幸的是,如果没有directly modifying IPython,我看不到如何做到这一点(尽管这种变化非常微不足道)。我会玩一会儿,看看它是否适合我的所有需求。【参考方案13】:

我使用非常务实的方法;这适用于几个笔记本,在几个方面。它甚至使我能够“转移”笔记本。它适用于 Windows 和 Unix/MacOS。 阿尔觉得很简单,就是解决上面的问题……

概念

基本上,跟踪.ipnyb-文件,只跟踪相应的.py-文件。 通过使用--script 选项启动notebook-server,在保存笔记本时会自动创建/保存该文件。

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

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

现在追踪历史很简单; .py 很小,有文字且易于区分。有时,我们需要一个克隆(只是分支;在那里启动第二个笔记本服务器)或旧版本(签出并导入笔记本服务器)等等。

提示和技巧

*.ipynb 添加到“.hgignore”,以便 Mercurial 知道它可以忽略这些文件 创建一个 (bash) 脚本来启动服务器(使用 --script 选项)并对其进行版本跟踪 保存笔记本确实保存了.py-文件,但将其签入。 这是一个缺点:人们可以忘记这一点 这也是一个功能:无需对存储库历史进行集群即可保存笔记本(并稍后继续)。

愿望

如果笔记本仪表板中有一个用于签入/添加/等的按钮会很不错 结帐到(通过示例)file@date+rev.py)应该会有所帮助 添加它会做很多工作;也许我会这样做一次。到目前为止,我只是手动完成。

【讨论】:

如何从.py 文件返回到笔记本?我喜欢这种方法,但因为.ipynb -> .py -> .ipynb 可能有损耗,所以我没有认真考虑这一点。 这很简单:加载它,例如将它放到笔记本仪表板上。除了“输出数据”,什么都没有丢失 如果这是真的,那么我认为这很接近想法,但我似乎记得 IPython 没有承诺在从 .py.ipynb 格式的转换中完全保留数据。有一个issue about this - 所以也许这将构成一个完整解决方案的基础。 我在将 .py 文件转换为 .ipynb 文件时遇到了一些困难。 nbconvert 似乎还不支持这一点,而且我没有笔记本仪表板,因为我手动运行 ipython notebook。对于如何实现这种向后转换,您有什么一般性建议吗? .py-to-notebook 转换肯定不是为了往返。所以这并不是一个通用的解决方案,尽管它很适合你。【参考方案14】:

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

https://github.com/***isme/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 存储库。它本质上是

的包装器
# 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"

还有更多方法,可以配置为在每个阶段需要或多或少的用户输入,但这是大体思路。

【讨论】:

【参考方案15】:

我还将向其他人添加https://nbdev.fast.ai/,这是最先进的“文学编程环境,正如 Donald Knuth 在 1983 年所设想的那样!”。

它还有一些 git 钩子可以帮助https://nbdev.fast.ai/#Avoiding-and-handling-git-conflicts 和其他命令,例如:

nbdev_read_nbs nbdev_clean_nbs nbdev_diff_nbs nbdev_test_nbs

因此,您还可以在编写库时随时随地创建文档,例如其中一些:

https://dev.fast.ai/ https://ohmeow.github.io/blurr/ https://rbracco.github.io/fastai2_audio/

除了第一个链接之外,您还可以在 nbdev tutorial 观看视频。

【讨论】:

我没有机会深入研究,但这似乎不支持 Knuth 所说的“缠结”,这是识字编程的主要观点之一。这使您可以按照对解释有意义的顺序编写代码,同时保留磁盘上所需的适当顺序。例如14_callback.schedule.ipynb 似乎从导入语句开始——代码中最不重要的部分。缠结允许您将其推迟到描述主要概念之后。 好吧,不太确定它是否确实处理tangling,但从该文件生成的“真实”python 文件是fastai2/callback/schedule.py,我添加了一个我没看过的youtube 视频。 【参考方案16】:

如果您遇到这样的 Unicode 解析错误,请跟进 Pietro Battiston 的出色脚本:

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')

【讨论】:

【参考方案17】:

挖了一圈,终于找到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

【讨论】:

我会注意到这个解决方案永远不会将任何输出保存到磁盘,并且在某种程度上独立于版本控制问题。【参考方案18】:

这是 2020 年 4 月,有很多用于 Jupyter 笔记本版本控制的策略和工具。以下是您可以使用的所有工具的快速概览,

nbdime - 非常适合笔记本的本地差异和合并

nbstripout - 一个 git 过滤器,用于在每次提交之前自动删除笔记本输出

jupytext - 将 .py 配套文件同步到每个笔记本。你只提交 .py 文件

nbconvert - 将笔记本转换为 python 脚本或 HTML(或两者)并提交这些备用文件类型

ReviewNB - 显示 GitHub 上任何提交或拉取请求的笔记本差异(连同输出)。还可以在笔记本单元格上编写 cmets 来讨论更改(下面的屏幕截图)。

免责声明:我构建了 ReviewNB。

【讨论】:

【参考方案19】:

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

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

现在,在签出 repo 后,只需运行 python py_file_to_notebooks.py 即可生成 ipynb 文件。切换分支后,您可能需要运行python py_file_to_notebooks.py -ov 来覆盖现有的 ipynb 文件。

为了安全起见,最好也添加 *.ipynb 到您的 .gitignore 文件中。

编辑:我不再这样做了,因为 (A) 每次签出分支时,您都必须从 py 文件重新生成笔记本,并且 (B) 笔记本中还有其他东西,例如您丢失的 markdown。我改为使用 git 过滤器从笔记本中剥离输出。讨论如何做到这一点是here。

【讨论】:

我喜欢这个想法,但经过测试,发现从.py 文件转换回.ipynb 是有问题的,特别是对于还没有转换器的版本4 笔记本。目前需要使用 v3 导入器,然后转换为 v4,我有点担心这个复杂的行程。此外,如果笔记本主要是 Julia 代码,.py 文件也不是一个很好的选择!最后,--script 已被弃用,所以我认为钩子是要走的路。 您链接中的 git 过滤器解决方案很好,您应该从此处复制您的答案:-)【参考方案20】:

好的,根据here 的讨论,目前最好的解决方案是制作一个 git 过滤器,以便在提交时自动去除 ipynb 文件的输出。

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

当您无法导入最新的 IPython 时,我稍微修改了 cfriedline 的 nbstripout 文件以提供信息性错误: https://github.com/petered/plato/blob/fb2f4e252f50c79768920d0e47b870a8d799e92b/notebooks/config/strip_notebook_output 并将其添加到我的仓库中,比如./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 的本地路径。

【讨论】:

【参考方案21】:

您可以使用这个 jupyter 扩展。它将使您能够直接将您的 ipython 笔记本上传到 github。

https://github.com/sat28/githubcommit

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

【讨论】:

你能解释一下这是做什么的吗?记录不是特别清楚。 @AlexMonras 这将直接在 jupyter notebook 中添加一个按钮,您可以从该按钮将 notebook 推送到您的 GitHub 存储库并带有提交消息【参考方案22】:

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

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

【讨论】:

以上是关于在版本控制下使用 IPython / Jupyter Notebooks的主要内容,如果未能解决你的问题,请参考以下文章

在 IPython 控制台中运行内核的问题

Ubuntu环境下IPython的搭建和使用

PythonXY,IPython Qt 控制台,matplotlib,绘制不在内联模式下的东西

Spyder IDE 中的 iPython 控制台挂起任何代码执行

如何在 Anaconda 2.0 中使用 Python 3.4 激活 Ipython Notebook 和 QT 控制台

ipython在最新python版本中出现事件循环问题