Usando blocos de anotações IPython sob controle de versão


569

Qual é uma boa estratégia para manter os notebooks IPython sob controle de versão?

O formato do notebook é bastante acessível para o controle de versão: se alguém deseja controlar o notebook e as saídas, ele funciona muito bem. O aborrecimento ocorre quando se quer apenas controlar a versão da entrada, excluindo as saídas de célula (também conhecidas como "produtos de construção") que podem ser grandes bolhas binárias, especialmente para filmes e plotagens. Em particular, estou tentando encontrar um bom fluxo de trabalho que:

  • permite escolher entre incluir ou excluir a saída,
  • me impede de confirmar acidentalmente a saída, se eu não a quiser,
  • me permite manter a saída na minha versão local,
  • permite que eu veja quando tenho alterações nas entradas usando meu sistema de controle de versão (ou seja, se eu controlo apenas as entradas, mas meu arquivo local tem saídas, gostaria de poder ver se as entradas foram alteradas (exigindo uma confirmação O uso do comando status do controle de versão sempre registra uma diferença, pois o arquivo local possui saídas.)
  • permite atualizar meu notebook de trabalho (que contém a saída) de um notebook limpo e atualizado. (atualizar)

Como mencionado, se eu optar por incluir as saídas (o que é desejável ao usar o nbviewer, por exemplo), tudo está bem. O problema é quando eu não quero controlar a versão da saída. Existem algumas ferramentas e scripts para eliminar a saída do notebook, mas frequentemente encontro os seguintes problemas:

  1. Eu acidentalmente confirmo uma versão com a saída, poluindo assim meu repositório.
  2. Limpo a saída para usar o controle de versão, mas realmente prefiro manter a saída na minha cópia local (às vezes leva um tempo para reproduzir, por exemplo).
  3. Alguns dos scripts que retiram a saída alteram ligeiramente o formato em comparação com a Cell/All Output/Clearopção de menu, criando assim ruídos indesejados nos diffs. Isso é resolvido por algumas das respostas.
  4. Ao puxar alterações para uma versão limpa do arquivo, preciso encontrar uma maneira de incorporar essas alterações no meu notebook de trabalho sem precisar executar novamente tudo. (atualizar)

Eu considerei várias opções que discutirei abaixo, mas ainda não encontrei uma boa solução abrangente. Uma solução completa pode exigir algumas alterações no IPython ou depender de alguns scripts externos simples. Atualmente, uso mercurial , mas gostaria de uma solução que também funcione com o git : uma solução ideal seria independente do controle de versão.

Esse problema foi discutido várias vezes, mas não há uma solução definitiva ou clara da perspectiva do usuário. A resposta a esta pergunta deve fornecer a estratégia definitiva. Não há problema em exigir uma versão recente (mesmo em desenvolvimento) do IPython ou uma extensão facilmente instalada.

Atualização: Eu brinquei com a versão modificada do meu notebook, que opcionalmente salva uma .cleanversão a cada salvamento, usando as sugestões de Gregory Crosswhite . Isso satisfaz a maioria das minhas restrições, mas deixa o seguinte não resolvido:

  1. Essa ainda não é uma solução padrão (requer uma modificação da fonte ipython. Existe uma maneira de obter esse comportamento com uma extensão simples? Precisa de algum tipo de gancho para salvar.
  2. Um problema que tenho no fluxo de trabalho atual está provocando alterações. Eles entrarão no .cleanarquivo e precisam ser integrados de alguma forma à minha versão de trabalho. (Obviamente, eu sempre posso reexecutar o notebook, mas isso pode ser uma dor, especialmente se alguns dos resultados dependem de cálculos longos, cálculos paralelos etc.). Ainda não tenho uma boa idéia sobre como resolver isso. . Talvez um fluxo de trabalho envolvendo uma extensão como o ipycache possa funcionar, mas isso parece um pouco complicado.

Notas

Remoção (remoção) da saída

  • Quando o notebook está funcionando, pode-se usar a Cell/All Output/Clearopção de menu para remover a saída.
  • Existem alguns scripts para remover a saída, como o script nbstripout.py, que remove a saída, mas não produz a mesma saída que a interface do notebook. Eventualmente, isso foi incluído no repositório ipython / nbconvert , mas foi encerrado, informando que as alterações agora estão incluídas no ipython / ipython , mas a funcionalidade correspondente parece não ter sido incluída ainda. (update) Dito isto, a solução de Gregory Crosswhite mostra que isso é bastante fácil de fazer, mesmo sem chamar ipython / nbconvert, portanto, essa abordagem provavelmente é viável se puder ser conectada adequadamente. (Anexá-la a cada sistema de controle de versão, no entanto, não parece uma boa idéia - isso deve, de alguma forma, conectar-se ao mecanismo do notebook).

Grupos de Notícias

Problemas

Solicitações Pull


Parece uma ótima coisa para adicionar como problema no github.com/ipython/ipython ou enviar uma solicitação de recebimento que o ajude a promover esse objetivo.
Kyle Kelley

4
Depois de ter um script de trabalho para remover a saída, você pode usar um filtro "limpo" do Git para aplicá-lo automaticamente antes de confirmar (consulte filtros de limpeza / borrão).
Matthias

1
@foobarbecue A questão contém soluções insatisfatórias: cada uma tem pelo menos uma limitação. Agora que o PR 4175 foi fundido, uma solução completa provavelmente pode ser formulada, mas isso ainda precisa ser feito. Assim que tiver algum tempo, farei isso (como resposta) se outra pessoa não fornecer uma solução satisfatória nesse meio tempo.
mforbes

1
@saroele Ainda não encontrei uma solução recomendada: Eu estava indo com a --scriptopção, mas que foi removida. Estou esperando até que os ganchos pós-salvamento sejam implementados ( que são planejados ) e, nesse ponto, acho que poderei fornecer uma solução aceitável combinando várias das técnicas.
Mforbes

1
@mforbes Parece que o PR foi fundido poucos dias após o seu comentário. Você ou alguém com mais conhecimento do que eu poderia postar aqui uma resposta que mostre como usar o novo recurso?
KobeJohn

Respostas:


124

Aqui está a minha solução com o git. Ele permite que você adicione e confirme (e diff) como de costume: essas operações não alterarão sua árvore de trabalho e, ao mesmo tempo (re) executar um notebook, não alterará seu histórico do git.

Embora isso possa provavelmente ser adaptado a outros VCSs, eu sei que ele não atende aos seus requisitos (pelo menos a agnosticidade do VSC). Ainda assim, é perfeito para mim e, embora não seja nada particularmente brilhante, e muitas pessoas provavelmente já o usam, não encontrei instruções claras sobre como implementá-lo pesquisando no Google. Portanto, pode ser útil para outras pessoas.

  1. Salve um arquivo com este conteúdo em algum lugar (para o seguinte, vamos assumir ~/bin/ipynb_output_filter.py)
  2. Torne executável ( chmod +x ~/bin/ipynb_output_filter.py)
  3. Crie o arquivo ~/.gitattributes, com o seguinte conteúdo

    *.ipynb    filter=dropoutput_ipynb
    
  4. Execute os seguintes comandos:

    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
    

Feito!

Limitações:

  • funciona apenas com git
  • no git, se você estiver no ramo somebranche o faz git checkout otherbranch; git checkout somebranch, geralmente espera que a árvore de trabalho permaneça inalterada. Aqui, em vez disso, você terá perdido a numeração de saída e de células dos notebooks cuja origem difere entre os dois ramos.
  • Em geral, a saída não é versionada, como na solução de Gregory. Para não jogá-lo fora toda vez que você faz algo que envolva um checkout, a abordagem pode ser alterada armazenando-o em arquivos separados (mas observe que no momento em que o código acima é executado, o ID de confirmação não é conhecido!), e, possivelmente, versioná-los (mas observe que isso exigiria algo mais que a git commit notebook_file.ipynb, embora ao menos se mantivesse git diff notebook_file.ipynblivre do lixo base64).
  • Dito isto, aliás, se você extrair código (ou seja, cometido por outra pessoa que não esteja usando essa abordagem) que contenha alguma saída, a saída será feita normalmente. Somente a saída produzida localmente é perdida.

Minha solução reflete o fato de que eu pessoalmente não gosto de manter o material gerado com versão - observe que fazer fusões envolvendo a saída é quase garantido para invalidar a saída ou sua produtividade ou ambas.

EDITAR:

  • se você adotar a solução como sugeri, ou seja, globalmente, você terá problemas no caso de algum repositório git que deseja versão de saída. Portanto, se você deseja desativar a filtragem de saída para um repositório git específico, basta criar dentro dele um arquivo .git / info / attribute , com

    **. ipynb filter =

como conteúdo. Claramente, da mesma maneira, é possível fazer o oposto: ativar a filtragem apenas para um repositório específico.

  • o código agora é mantido em seu próprio repositório git

  • se as instruções acima resultarem em ImportErrors, tente adicionar "ipython" antes do caminho do script:

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

EDIT : maio de 2016 (atualizado em fevereiro de 2017): existem várias alternativas ao meu script - por uma questão de integridade, aqui está uma lista das que eu conheço: nbstripout ( outras variantes ), nbstrip , jq .


2
Como você lida com a questão de incorporar as mudanças que você realiza? Você vive com a necessidade de regenerar toda a saída? (Eu acho que esta é uma manifestação de sua segunda limitação.)
mforbes

1
@zhermes: esta versão estendida deve ser OK
Pietro Battiston

1
Existe uma maneira de usar esse método de filtros git com uma ferramenta de comparação externa? O filtro será aplicado se eu usar a ferramenta de linha de comando normal, mas não se estiver usando o meld como uma ferramenta diff. stackoverflow.com/q/30329615/578770
FA

1
Para evitar ImportErrorque eu tinha alter ao acima para executar usando ipython:git config --global filter.dropoutput_ipynb.clean ipython ~/bin/ipynb_output_filter.py
chris838

1
Solução impressionante Pietro, obrigado :) Eu mudei duas coisas ao usar seu script no meu caso: 1) Preferi declarar o filtro em .gitattributes na raiz do repositório, em vez de ~/.gitattributes, outras pessoas têm os mesmos filtros que eu 2 ) Eu defini o regexp como workdir/**/*.ipynb filter=dropoutput_ipynbe coloco a maioria dos meus cadernos no workdir / => se ainda quero enviar um notebook com a saída e aproveitar a renderização que pode ser marcada no github, basta colocá-lo fora dessa pasta.
Svend

63

Temos um projeto colaborativo em que o produto é o Jupyter Notebooks e usamos uma abordagem nos últimos seis meses que funciona muito bem: ativamos o salvamento dos .pyarquivos automaticamente e rastreamos os .ipynbarquivos e os .pyarquivos.

Dessa forma, se alguém quiser visualizar / baixar o bloco de anotações mais recente, poderá fazê-lo via github ou nbviewer, e se alguém quiser ver como o código do bloco de anotações foi alterado, basta ver as alterações nos .pyarquivos.

Para Jupyterservidores notebook , isso pode ser feito adicionando as linhas

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

para o jupyter_notebook_config.pyarquivo e reiniciando o servidor do notebook.

Se você não tiver certeza de qual diretório localizar o jupyter_notebook_config.pyarquivo, digite jupyter --config-dire, se não encontrar o arquivo, poderá criá-lo digitando jupyter notebook --generate-config.

Para Ipython 3servidores notebook , isso pode ser feito adicionando as linhas

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

para o ipython_notebook_config.pyarquivo e reiniciando o servidor do notebook. Essas linhas são de um problema do github, que a resposta é @minrk fornecida e o @dror as inclui na resposta SO também.

Para Ipython 2servidores notebook , isso pode ser feito iniciando o servidor usando:

ipython notebook --script

ou adicionando a linha

c.FileNotebookManager.save_script = True

para o ipython_notebook_config.pyarquivo e reiniciando o servidor do notebook.

Se você não tiver certeza de qual diretório localizar o ipython_notebook_config.pyarquivo, digite ipython locate profile defaulte, se não encontrar o arquivo, poderá criá-lo digitando ipython profile create.

Aqui está o nosso projeto no github que está usando essa abordagem : e aqui está um exemplo do github de explorar alterações recentes em um notebook .

Ficamos muito felizes com isso.


1
Obrigado pela evidência adicional de que o uso --scriptfuncionou na prática. O problema é que os notebooks reais podem ser enormes se as imagens forem mantidas. Uma solução ideal nesse caminho pode usar algo como o anexo-git para acompanhar apenas o último notebook completo.
Mforbes 11/09/14

No Ipython 3.x, o arquivo --scriptfoi descontinuado. ipython.org/ipython-doc/3/whatsnew/version3.html
Dror

Obrigado @dror, atualizei minha resposta para fornecer a solução ipython 3.x do minrk, como você também forneceu aqui.
Ricos Signell

10
Atualização: Esta solução está quebrada no iPython versão 4, devido a "The Big Split" do Jupyter do iPython. Para ajustar esta solução à versão 4, use o comando jupyter notebook --generate-configpara criar um arquivo de configuração. O comando jupyter --config-dirdescobre qual diretório contém os arquivos de configuração. E o trecho de código fornecido por @Rich deve ser adicionado ao arquivo nomeado jupyter_notebook_config.py. O resto funciona como antes.
bolinho mobius

2
Além do ponto de @mobiusdumpling, substitua por check_call(['ipython'com check_call(['jupyter', caso contrário, você receberá um aviso que ipython nbconvertfoi descontinuado e você deve usá-lo jupyter nbconvert. (Jupyter v4.1.0, iPython v4.1.2)
cutculus

36

Eu criei nbstripout, com base na essência do MinRKs , que suporta tanto o Git quanto o Mercurial (graças a mforbes). Ele deve ser usado de forma independente na linha de comando ou como um filtro, que é facilmente (des) instalado no repositório atual via nbstripout install/ nbstripout uninstall.

Obtenha do PyPI ou simplesmente

pip install nbstripout

Estou considerando um fluxo de trabalho em que mantenho .ipynb e .py correspondentes criados automaticamente usando os ganchos de pós-salvamento descritos acima. Gostaria de usar .py para diffs - o nbstripout poderá limpar o arquivo .py dos contadores de execução de célula (# In [1] alterado para In [*]), para que eles não atrapalhem as diffs ou devo criar um script simples para fazer isso?
Krzysztof Słowiński

1
@ KrzysztofSłowiński Não, nbstripoutesse suporte não é fácil, pois depende do formato JSON do Notebook. Provavelmente, é melhor escrever um script especializado no seu caso de uso.
Kynan #


13

Após alguns anos removendo as saídas dos notebooks, tentei encontrar uma solução melhor. Agora uso o Jupytext , uma extensão para o Jupyter Notebook e o Jupyter Lab que eu projetei.

O Jupytext pode converter os blocos de anotações Jupyter em vários formatos de texto (Scripts, Markdown e R Markdown). E inversamente. Ele também oferece a opção de emparelhar um notebook a um desses formatos e sincronizar automaticamente as duas representações do notebook (um .ipynbe um .md/.py/.Rarquivo).

Deixe-me explicar como o Jupytext responde às perguntas acima:

permite escolher entre incluir ou excluir a saída,

O .md/.py/.Rarquivo contém apenas as células de entrada. Você sempre deve acompanhar este arquivo. Versão do .ipynbarquivo somente se você deseja rastrear as saídas.

me impede de confirmar acidentalmente a saída, se eu não a quiser,

Adicionar *.ipynba.gitignore

me permite manter a saída na minha versão local,

As saídas são preservadas no .ipynbarquivo (local)

permite que eu veja quando tenho alterações nas entradas usando meu sistema de controle de versão (ou seja, se eu controlo apenas as entradas, mas meu arquivo local tem saídas, gostaria de poder ver se as entradas foram alteradas (exigindo uma confirmação O uso do comando status do controle de versão sempre registra uma diferença, pois o arquivo local possui saídas.)

O diff no arquivo .py/.Rou .mdé o que você está procurando

permite atualizar meu notebook de trabalho (que contém a saída) de um notebook limpo e atualizado. (atualizar)

Puxe a revisão mais recente do arquivo .py/.Rou .mde atualize seu notebook no Jupyter (Ctrl + R). Você obterá as células de entrada mais recentes do arquivo de texto, com saídas correspondentes do .ipynbarquivo. O kernel não é afetado, o que significa que suas variáveis ​​locais são preservadas - você pode continuar trabalhando onde parou.

O que eu adoro no Jupytext é que o notebook (sob a forma de um .py/.Rou .mdarquivo) pode ser editado no seu IDE favorito. Com essa abordagem, a refatoração de um notebook se torna fácil. Quando terminar, basta atualizar o notebook no Jupyter.

Se você quiser experimentá-lo: instale o Jupytext pip install jupytexte reinicie o editor do Jupyter Notebook ou Lab. Abra o bloco de notas que você deseja controlar a versão e emparelhe-o com um arquivo Markdown (ou um Script) usando o Menu Jupytext no bloco de anotações Jupyter (ou os comandos Jupytext no Jupyter Lab). Salve o seu notebook e você obterá os dois arquivos: o original .ipynb, além da representação de texto prometida do notebook, que é perfeita para o controle de versão!

Para aqueles que podem estar interessados: o Jupytext também está disponível na linha de comando .


13

Atualização : Agora você pode editar os arquivos do Jupyter Notebook diretamente no Código do Visual Studio. Você pode optar por editar o notebook ou o arquivo python convertido.

Eu finalmente encontrei uma maneira produtiva e simples de fazer Jupyter e Git tocar bem juntos. Ainda estou nos primeiros passos, mas já acho que é muito melhor do que todas as outras soluções complicadas.

O Visual Studio Code é um editor legal e de código-fonte aberto da Microsoft. Possui uma excelente extensão Python que agora permite importar um Notebook Jupyter como código python. Agora você também pode editar diretamente os Jupyter Notebooks .

Depois de importar o seu notebook para um arquivo python, todo o código e descontos serão reunidos em um arquivo python comum, com marcadores especiais nos comentários. Você pode ver na imagem abaixo:

Editor VSCode com um notebook convertido em python

Seu arquivo python apenas possui o conteúdo das células de entrada do notebook. A saída será gerada em uma janela dividida. Você tem código puro no notebook, ele não muda enquanto você o executa. Nenhuma saída misturada com o seu código. Nenhum formato incompreensível JSON estranho para analisar suas diferenças.

Apenas código python puro, onde você pode identificar facilmente todas as diferenças.

Eu nem preciso mais versão meus .ipynbarquivos. Eu posso colocar uma *.ipynblinha .gitignore.

Precisa gerar um notebook para publicar ou compartilhar com alguém? Não tem problema, basta clicar no botão de exportação na janela interativa do python

Exportando um arquivo python para o formato Notebook

Se você estiver editando o notebook diretamente, agora existe um ícone Convert and save to a python script. Ícones do Jupyter no Visual Studio Code

Aqui está uma captura de tela de um notebook dentro do Visual Studio Code:

Editando o Notebook no VSCode

Estou usando há apenas um dia, mas finalmente posso usar o Jupyter com o Git.

PS: A conclusão do código VSCode é muito melhor que o Jupyter.


12

(2017-02)

estratégias

  • on_commit ():
    • retire a saída> name.ipynb ( nbstripout,)
    • retire a saída> name.clean.ipynb ( nbstripout,)
    • sempre nbconvertpara python: name.ipynb.py ( nbconvert)
    • sempre converter em markdown: name.ipynb.md ( nbconvert, ipymd)
  • vcs.configure ():
    • git difftool, mergetool: nbdiff e nbmerge de nbdime

Ferramentas


11

As respostas muito populares de 2016 acima são hacks inconsistentes em comparação com a melhor maneira de fazer isso em 2019.

Existem várias opções, a melhor que responde à pergunta é o Jupytext.

Jupytext

Veja o artigo Towards Data Science no Jupytext

A maneira como funciona com o controle de versão é colocar os arquivos .py e .ipynb no controle de versão. Veja o .py se desejar a diferença de entrada, veja o .ipynb se desejar a saída renderizada mais recente.

Menções notáveis: VS studio, nbconvert, nbdime, hidrogênio

Eu acho que com um pouco mais de trabalho, o VS Studio e / ou o hidrogênio (ou similar) se tornarão os atores dominantes na solução desse fluxo de trabalho.


9

Basta encontrar o "jupytext", que parece uma solução perfeita. Ele gera um arquivo .py do notebook e mantém os dois em sincronia. Você pode controlar as versões, diferenciar e mesclar entradas através do arquivo .py sem perder as saídas. Quando você abre o notebook, ele usa o .py para células de entrada e o .ipynb para saída. E se você quiser incluir a saída no git, basta adicionar o ipynb.

https://github.com/mwouts/jupytext


9

Como existem tantas estratégias e ferramentas para lidar com o controle de versão de notebooks, tentei criar um fluxograma para escolher uma estratégia adequada (criada em abril de 2019)

Fluxo de decisão para escolher a estratégia de controle de versão


8

Conforme apontado por, o --scriptitem foi descontinuado em 3.x. Essa abordagem pode ser usada aplicando um gancho pós-salvamento. Em particular, adicione o seguinte a 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

O código é retirado do número 8009 .


Obrigado por demonstrar o uso de um gancho pós-salvamento. Infelizmente, como mencionado anteriormente, voltar do .pyarquivo para um notebook é problemático, portanto, infelizmente, essa não é uma solução completa. (Eu meio que gostaria que fosse, pois é muito bom para diff .pyarquivos em vez de notebooks Talvez o novo. Notebook diff recurso será útil.
mforbes

1
Obrigado! Agora estou usando esse truque para reproduzir o --scriptcomportamento, independentemente do controle de versão. Eu tive alguns problemas no começo, então, caso eu possa economizar tempo para alguém: 1) Se ipython_notebook_config.pyestiver faltando na pasta de perfil, corra ipython profile createpara gerá-lo. 2) Se parecer que o gancho pós-salvamento foi ignorado, execute o ipython com --debugpara diagnosticar o problema. 3) Se o script falhar com o erro ImportError: No module named mistune- instalação simples minstue: pip install mistune.
21315 Joe

7

Infelizmente, eu não sei muito sobre o Mercurial, mas posso oferecer uma possível solução que funcione com o Git, na esperança de que você possa traduzir meus comandos do Git em seus equivalentes do Mercurial.

Para segundo plano, no Git, o addcomando armazena as alterações que foram feitas em um arquivo em uma área intermediária. Depois de fazer isso, quaisquer alterações subseqüentes no arquivo serão ignoradas pelo Git, a menos que você solicite que as encaminhe também. Portanto, o script a seguir, que, para cada um dos arquivos fornecidos, retira todos os outputse prompt_number sections, prepara o arquivo retirado e restaura o original:

NOTA: Se você executar uma mensagem de erro assim ImportError: No module named IPython.nbformat, use-o ipythonpara executar o script em vez de 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)

Depois que o script for executado nos arquivos cujas alterações você deseja confirmar, basta executar git commit.


Obrigado pela sugestão. O Mercurial realmente não tem uma área de preparação como o git (embora se possa usar filas de mercurial para esse fim). Enquanto isso, tentei adicionar esse código a um gancho de salvamento que salva uma versão limpa com uma .cleanextensão. Infelizmente, não consegui ver como fazer isso sem modificar diretamente o IPython (embora essa alteração tenha sido bastante trivial). Vou brincar com isso por um tempo e ver se ele atende a todas as minhas necessidades.
Mforbes

6

Eu uso uma abordagem muito pragmática; que funcionam bem em vários notebooks, em vários lados. E até me permite 'transferir' notebooks. Funciona tanto para Windows como Unix / MacOS.
Al pensei que é simples, é resolver os problemas acima ...

Conceito

Basicamente, não rastreie os .ipnybarquivos-, apenas os .pyarquivos- correspondentes .
Ao iniciar o servidor notebook com a --scriptopção, esse arquivo é criado / salvo automaticamente quando o notebook é salvo.

Esses .pyarquivos contêm toda a entrada; o não código é salvo em comentários, assim como as bordas da célula. Esses arquivos podem ser lidos / importados (e arrastados) para o servidor notebook para (re) criar um notebook. Somente a saída se foi; até que seja reexecutado.

Pessoalmente, uso o mercurial para rastrear versão dos .pyarquivos; e use os comandos normais (linha de comando) para adicionar, fazer check-in (ect) para isso. A maioria dos outros (D) VCS permitirá isso.

É simples acompanhar a história agora; o .pysão pequenos, textual e simples diff. De vez em quando, precisamos de um clone (basta ramificar; iniciar um segundo notebook), ou uma versão mais antiga (check-out e importar para um servidor notebook), etc.

Dicas e truques

  • Adicione * .ipynb a ' .hgignore ', para que a Mercurial saiba que pode ignorar esses arquivos
  • Crie um script (bash) para iniciar o servidor (com a --scriptopção) e faça o controle de versão
  • Salvar um notebook salva o .pyarquivo-mas não o faz check-in.
    • Esta é uma desvantagem : pode-se esquecer que
    • Também é um recurso : é possível salvar um notebook (e continuar mais tarde) sem agrupar o histórico do repositório.

Desejos

  • Seria bom ter um botão para check-in / add / etc no painel do notebook
  • Um checkout para (por exemplo) file@date+rev.py) deve ser útil. Seria muito trabalho acrescentar isso; e talvez eu faça isso uma vez. Até agora, eu faço isso manualmente.

Como você vai do .pyarquivo de volta para um notebook? Eu gosto dessa abordagem, mas porque .ipynb-> .py-> .ipynbé potencialmente com perdas, não considerei isso seriamente.
Mforbes

Isso é fácil: carregue-o, por exemplo, soltando-o no painel do notebook. Exceto de "dados de saída" nada se perde
Albert

Se isso for verdade, acho que isso seria quase uma ideia, mas me lembro que o IPython não se comprometeu a preservar completamente os dados na transição .pypara os .ipynbformatos. Existe um problema sobre isso - talvez isso constitua a base para uma solução completa.
Mforbes

Estou tendo algumas dificuldades para converter .pyarquivos para .ipynbarquivos. nbconvertainda não parece oferecer suporte a isso e não tenho um painel de anotações desde que executo ipython notebookmanualmente. Você tem alguma sugestão geral sobre como implementar essa conversão para trás?
Mforbes 9/08/14

Certamente a .pytransformação de notebook em notebook não se destina a viagens de ida e volta. Portanto, isso realmente não pode ser uma solução geral, embora seja bom que funcione para você.
precisa saber é o seguinte

3

Para acompanhar o excelente script de Pietro Battiston, se você receber um erro de análise Unicode como este:

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)

Você pode adicionar no início do script:

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

3

Eu construí um pacote python que resolve esse problema

https://github.com/brookisme/gitnb

Ele fornece à CLI uma sintaxe inspirada no git para rastrear / atualizar / diferenciar notebooks dentro do seu repositório git.

Heres é um exemplo

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

Observe que a última etapa, na qual estou usando o "gitnb commit", está comprometendo seu repositório git. É essencialmente um invólucro para

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

Existem vários outros métodos, e podem ser configurados para que exijam mais ou menos entrada do usuário em cada estágio, mas essa é a ideia geral.


3

Depois de pesquisar, finalmente encontrei esse gancho de pré-gravação relativamente simples nos documentos do Jupyter . Ele retira os dados de saída da célula. Você precisa colá-lo no jupyter_notebook_config.pyarquivo (veja as instruções abaixo).

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

Da resposta de Rich Signell :

Se você não tiver certeza de qual diretório localizar seu jupyter_notebook_config.pyarquivo, digite jupyter --config-dir[no prompt de comando / terminal] e, se não encontrar o arquivo, poderá criá-lo digitando jupyter notebook --generate-config.


1
Eu observaria que essa solução nunca salvaria nenhuma saída no disco e é um pouco independente do problema de controle de versão.
bdforbes

2

Fiz o que Albert e Rich fizeram - Não faça a versão de arquivos .ipynb (pois eles podem conter imagens, o que fica confuso). Em vez disso, sempre execute ipython notebook --scriptou coloque c.FileNotebookManager.save_script = Trueseu arquivo de configuração, para que um (versionável).py arquivo seja sempre criado quando você salvar seu notebook.

Para regenerar os blocos de anotações (após fazer o check-out de um repositório ou alternar uma ramificação), coloquei o script py_file_to_notebooks.py no diretório em que armazeno meus blocos de anotações.

Agora, depois de verificar um repo, basta executar python py_file_to_notebooks.pypara gerar os arquivos ipynb. Após alternar a ramificação, talvez seja necessário executar python py_file_to_notebooks.py -ova substituição dos arquivos ipynb existentes.

Só por segurança, é bom também adicionar *.ipynbao seu.gitignore arquivo.

Edit: Eu não faço mais isso (A) porque você precisa regenerar seus blocos de anotações de arquivos py toda vez que faz check-out de uma filial e (B) há outras coisas, como descontos nos blocos de anotações que você perde. Em vez disso, retiro a saída dos notebooks usando um filtro git. A discussão sobre como fazer isso está aqui .


Gostei dessa idéia, mas após o teste, constatou que a conversão de .pyarquivos para trás .ipynbé problemática, especialmente nos notebooks da versão 4 para os quais ainda não há um conversor. No momento, seria necessário usar o importador v3 e depois converter para v4, e estou um pouco preocupado com essa viagem complicada. Além disso, um .pyarquivo não é uma escolha muito boa se o notebook for principalmente o código Julia! Finalmente, --scriptestá obsoleto, então acho que os ganchos são o caminho a percorrer.
Mforbes

A solução filtro git em seu link é bom, você deve copiar a resposta de lá aqui :-)
mcarans

2

Ok, então parece que a melhor solução atual, de acordo com uma discussão aqui , é criar um filtro git para remover automaticamente a saída dos arquivos ipynb no commit.

Aqui está o que eu fiz para fazê-lo funcionar (copiado dessa discussão):

Modifiquei levemente o arquivo nbstripout do cfriedline para fornecer um erro informativo quando você não pode importar o IPython mais recente: https://github.com/petered/plato/blob/fb2f4e252f50c79768920d0e47b870a8d799e92b/notebooks/config/strip_notebook_output E acrescentou : diga em./relative/path/to/strip_notebook_output

Também foi adicionado o arquivo .gitattributes à raiz do repositório, contendo:

*.ipynb filter=stripoutput

E criou um setup_git_filters.shcontendo

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

E correu source setup_git_filters.sh. A coisa chique de $ (git rev-parse ...) é encontrar o caminho local do seu repositório em qualquer máquina (Unix).


1

Essa extensão jupyter permite que os usuários enviem blocos de anotações jupyter diretamente para o github.

Por favor olhe aqui

https://github.com/sat28/githubcommit


você pode explicar o que isso faz? A duplicação não é especialmente clara.
Alex Monras

@AlexMonras Isso adicionará diretamente um botão no bloco de anotações jupyter, de onde você pode enviar blocos de anotações ao seu repositório do GitHub com uma mensagem de confirmação
sábado

1

Estamos em abril de 2020 e existem muitas estratégias e ferramentas para o controle de versão do notebook Jupyter. Aqui está uma rápida visão geral de todas as ferramentas que você pode usar,

  • nbdime - Agradável para difusão local e mesclagem de notebooks

  • nbstripout - Um filtro git para remover automaticamente as saídas do notebook antes de cada confirmação

  • jupytext - Mantém um arquivo complementar .py sincronizado com cada bloco de anotações. Você só confirma arquivos .py

  • nbconvert - Converte blocos de anotações em um script python ou HTML (ou ambos) e confirma esses tipos de arquivos alternativos

  • ReviewNB - Mostra o diff do notebook (junto com a saída) para qualquer solicitação de confirmação ou recebimento no GitHub. Também é possível escrever comentários nas células dos notebooks para discutir as alterações (captura de tela abaixo).

insira a descrição da imagem aqui

Disclaimer: Eu criei o ReviewNB.


0

Que tal a idéia discutida no post abaixo, onde a saída do notebook deve ser mantida, com o argumento de que pode levar muito tempo para gerá-lo, e é útil, já que o GitHub agora pode renderizar notebooks. Existem ganchos de salvamento automático adicionados para exportar arquivos .py, usados ​​para diffs e .html para compartilhar com membros da equipe que não usam blocos de anotações ou git.

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.