641 votes

Utilisation d'IPython / Jupyter Notebooks sous contrôle de version

Quelle est une bonne stratégie pour garder IPython des carnets de notes sous contrôle de version ?

Le format du carnet de notes se prête bien au contrôle de version : si l'on veut contrôler la version du carnet de notes et des résultats, cela fonctionne très bien. L'ennui vient lorsque l'on veut seulement contrôler la version de l'entrée, à l'exclusion des sorties de la cellule (alias "produits de construction") qui peuvent être de gros blobs binaires, en particulier pour les films et les intrigues. En particulier, j'essaie de trouver un bon flux de travail qui :

  • me permet de choisir entre inclure ou exclure la sortie,
  • m'empêche de commettre accidentellement des sorties si je ne le souhaite pas,
  • me permet de conserver la sortie dans ma version locale,
  • me permet de voir quand j'ai des changements dans les entrées en utilisant mon système de contrôle de version (i.e. si je ne contrôle que les entrées mais que mon fichier local a des sorties, alors j'aimerais pouvoir voir si les entrées ont changé (ce qui nécessite un commit). L'utilisation de la commande d'état du contrôle de version enregistrera toujours une différence puisque le fichier local a des sorties).
  • me permet de mettre à jour mon cahier de travail (qui contient la sortie) à partir d'un cahier propre mis à jour. (mise à jour)

Comme mentionné, si je choisis d'inclure les sorties (ce qui est souhaitable lorsque l'on utilise des nbviewer par exemple), alors tout va bien. Le problème est que lorsque je ne pas vous voulez contrôler la version de la sortie. Il existe quelques outils et scripts pour dépouiller la sortie du carnet de notes, mais je rencontre fréquemment les problèmes suivants :

  1. J'ai accidentellement commit une version avec la sortie, polluant ainsi mon référentiel.
  2. J'efface la sortie pour utiliser le contrôle de version, mais je préférerais vraiment garder la sortie dans ma copie locale (il faut parfois un certain temps pour reproduire par exemple).
  3. Certains des scripts qui dépouillent la sortie modifient légèrement le format par rapport à l'option Cell/All Output/Clear ce qui crée un bruit indésirable dans les diffs. Ce problème est résolu par certaines des réponses.
  4. Lorsque j'apporte des modifications à une version propre du fichier, je dois trouver un moyen d'incorporer ces modifications dans mon carnet de travail sans avoir à tout réexécuter. (mise à jour)

J'ai envisagé plusieurs options dont je parlerai ci-dessous, mais je n'ai pas encore trouvé de bonne solution globale. Une solution complète pourrait nécessiter quelques changements dans IPython, ou pourrait s'appuyer sur quelques simples scripts externes. J'utilise actuellement mercuriel mais j'aimerais une solution qui fonctionne aussi avec git Une solution idéale serait agnostique au contrôle de version.

Cette question a été discutée à de nombreuses reprises, mais il n'existe pas de solution définitive ou claire du point de vue de l'utilisateur. La réponse à cette question devrait fournir la stratégie définitive. Ce n'est pas grave si cela nécessite une version récente (même de développement) de IPython ou une extension facile à installer.

Mise à jour : J'ai joué avec mon carnet de notes modifié qui enregistre éventuellement une .clean avec chaque sauvegarde utilisant Les suggestions de Gregory Crosswhite . Cela satisfait la plupart de mes contraintes mais laisse les suivantes non résolues :

  1. Cette solution n'est pas encore standard (elle nécessite une modification du code source d'ipython). Existe-t-il un moyen d'obtenir ce comportement avec une simple extension ? Il faut une sorte de crochet on-save.
  2. Un problème que je rencontre avec le flux de travail actuel est de tirer les modifications. Ceux-ci arrivent dans le .clean et doivent ensuite être intégrés d'une manière ou d'une autre dans ma version de travail. (Bien sûr, je peux toujours réexécuter le notebook, mais cela peut être pénible, surtout si certains des résultats dépendent de longs calculs, de calculs en parallèle, etc. Peut-être qu'un flux de travail impliquant une extension comme ipycache pourrait fonctionner, mais cela semble un peu trop compliqué.

Notes

Retrait (stripping) Sortie

  • Lorsque le notebook est en cours d'exécution, on peut utiliser la fonction Cell/All Output/Clear pour supprimer la sortie.
  • Il existe quelques scripts pour supprimer la sortie, comme le scripts. nbstripout.py qui supprime la sortie, mais ne produit pas la même sortie qu'en utilisant l'interface du notebook. Ceci a finalement été inclus dans la version ipython/nbconvert mais il a été clôturé en indiquant que les modifications sont maintenant incluses dans le répertoire de l'entreprise. ipython/ipython mais la fonctionnalité correspondante ne semble pas encore avoir été incluse. (mise à jour) Ceci étant dit, La solution de Gregory Crosswhite montre que cela est assez facile à faire, même sans invoquer ipython/nbconvert Cette approche est donc probablement réalisable si elle peut être connectée correctement. (L'attacher à chaque système de contrôle de version, cependant, ne semble pas être une bonne idée - cela devrait d'une manière ou d'une autre s'accrocher au mécanisme du carnet de notes).

Groupes de discussion

Questions

Pull Requests

0 votes

Cela semble être une bonne chose à ajouter en tant que problème sur le site Web de la Commission européenne. github.com/ipython/ipython ou soumettez une demande de retrait qui vous aide à atteindre cet objectif.

0 votes

Comme vous pouvez le constater, il existe déjà une pléthore de relations publiques et de questions relatives à cet objectif. Une fois ceux-ci résolus (à savoir PR 4175 ), alors une réponse définitive devrait être disponible, mais elle impliquera probablement des scripts supplémentaires en dehors d'IPython (des hooks git ou hg par exemple). Par conséquent, je ne pense pas qu'il y aura quoi que ce soit à gagner en ajoutant un nouveau PR ou problème.

0 votes

Oui, leur développement progresse rapidement et régulièrement chaque jour. Mais les développeurs sont de bonnes personnes (et ont probablement lu ce message). Je sais que je veux un flux de travail facile pour travailler avec git.

8voto

Dror Points 953

Comme l'a souligné le --script est déprécié dans 3.x . Cette approche peut être utilisée en appliquant un post-save-hook. En particulier, ajoutez ce qui suit à 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

Le code est tiré de #8009 .

7voto

Malheureusement, je ne connais pas bien Mercurial, mais je peux vous donner une solution possible qui fonctionne avec Git, dans l'espoir que vous puissiez traduire mes commandes Git en leurs équivalents Mercurial.

Pour rappel, dans Git, le add stocke les modifications qui ont été apportées à un fichier dans une zone de transit. Une fois que vous avez fait cela, toute modification ultérieure du fichier est ignorée par Git, à moins que vous ne lui demandiez de les mettre en scène également. Ainsi, le script suivant, qui, pour chacun des fichiers donnés, supprime toutes les modifications apportées au fichier. outputs y prompt_number sections , met en scène le fichier dépouillé, puis restaure l'original :

NOTE : Si vous obtenez un message d'erreur comme ImportError: No module named IPython.nbformat puis utiliser ipython pour exécuter le script au lieu 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)

Une fois que le script a été exécuté sur les fichiers dont vous vouliez commettre les changements, il suffit d'exécuter git commit .

0 votes

Merci pour la suggestion. Mercurial n'a pas vraiment de zone de transit comme git (bien que l'on puisse utiliser files d'attente mercuriales à cette fin). Entre-temps, j'ai essayé d'ajouter ce code à un hook de sauvegarde qui enregistre une version propre avec une balise .clean extension. Malheureusement, je n'ai pas vu comment faire cela sans modifier directement IPython (bien que ce changement ait été assez trivial). Je vais jouer avec cela pendant un certain temps et voir si cela répond à tous mes besoins.

7voto

Albert Points 71

J'utilise une approche très pragmatique ; qui fonctionne bien pour plusieurs carnets, à plusieurs côtés. Et cela me permet même de "transférer" des carnets entre eux. Cela fonctionne aussi bien sous Windows que sous Unix/MacOS.
Al pensait que c'est simple, c'est résoudre les problèmes ci-dessus...

Concept

En gros, faites pas suivre le .ipnyb -seuls les fichiers .py -files.
En commençant le ordinateur portable-serveur avec le --script ce fichier est automatiquement créé/sauvegardé lorsque le cahier est sauvegardé.

Ces .py -Les fichiers contiennent toutes les entrées ; le non-code est enregistré dans les commentaires, tout comme les bordures de cellules. Ces fichiers peuvent être lus/importés ( et glissés) dans le notebook-server pour (re)créer un notebook. Seule la sortie est perdue ; jusqu'à ce qu'elle soit ré-exécutée.

Personnellement, j'utilise mercuriel pour suivre la version du .py et utiliser les commandes normales (en ligne de commande) pour ajouter, enregistrer (ect) pour cela. La plupart des autres (D)VCS permettent cela.

Il est facile de retracer l'histoire maintenant ; le .py sont petites, textuelles et simples à différencier. De temps en temps, nous avons besoin d'un clone (branchez simplement ; démarrez un deuxième notebook-sever là), ou d'une version plus ancienne (vérifiez-la et importez-la dans un notebook-server), etc.

Conseils et astuces

  • Ajouter *.ipynb à .hgignore ', afin que Mercurial sache qu'il peut ignorer ces fichiers.
  • Créez un script (bash) script pour démarrer le serveur (avec l'option --script ) et faire le suivi de la version
  • La sauvegarde d'un carnet de notes permet de sauvegarder le .py -mais fait pas Vérifiez-le.
    • Il s'agit d'un inconvénient : On peut oublier que
    • C'est un fonctionnalité aussi : Il est possible de sauvegarder un carnet de notes (et de continuer plus tard) sans regrouper l'historique du référentiel.

Souhaits

  • Il serait bien d'avoir des boutons pour l'enregistrement, l'ajout, etc. dans le tableau de bord du carnet de notes.
  • Une caisse pour (par exemple) file@date+rev.py ) devrait être utile Ce serait trop de travail d'ajouter cela ; et peut-être que je le ferai une fois. Jusqu'à présent, je le fais simplement à la main.

0 votes

Comment passe-t-on du .py pour revenir à un carnet de notes ? J'aime cette approche, mais parce que .ipynb -> .py -> .ipynb est potentiellement avec perte, je n'ai pas considéré cela sérieusement.

0 votes

C'est facile : chargez-le, par exemple en le déposant sur le tableau de bord du Notebook. A l'exception des "données de sortie", rien n'est perdu.

0 votes

Si c'est vrai, alors je pense que ce serait proche de l'idée, mais je crois me souvenir qu'IPython ne s'est pas engagé à préserver complètement les données dans la transition de .py a .ipynb formats. Il existe un question à ce sujet - alors peut-être cela constituera-t-il la base d'une solution complète.

5voto

tyoc213 Points 318

Je vais aussi ajouter à ce que d'autres ont suggéré https://nbdev.fast.ai/ qui est un "environnement de programmation lettré" à la pointe de la technologie, tel qu'imaginé par Donald Knuth en 1983 !

Il dispose également de quelques hooks git qui aident un peu https://nbdev.fast.ai/#Avoiding-and-handling-git-conflicts et les autres commandes comme :

  • nbdev_read_nbs
  • nbdev_clean_nbs
  • nbdev_diff_nbs
  • nbdev_test_nbs

Vous pouvez donc aussi créer votre documentation en cours de route, par exemple en écrivant une bibliothèque :

Vous pouvez voir une vidéo ici tutoriel nbdev à part le premier lien.

4voto

dennis-360ict Points 15

J'ai construit un paquet python qui résout ce problème.

https://github.com/brookisme/gitnb

Il fournit une CLI avec une syntaxe inspirée de git pour suivre/mettre à jour/diffuser les carnets de notes dans votre dépôt git.

Voici un exemple

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

Notez que la dernière étape, où j'utilise "gitnb commit", consiste à commiter votre dépôt git. C'est essentiellement une enveloppe pour

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

Il existe plusieurs autres méthodes, et elles peuvent être configurées de manière à ce que l'utilisateur ait plus ou moins à intervenir à chaque étape, mais c'est l'idée générale.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X