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.

136voto

Pietro Battiston Points 423

Voici ma solution avec git. Elle vous permet de simplement ajouter et commiter (et diff) comme d'habitude : ces opérations ne modifieront pas votre arbre de travail, et en même temps (re)lancer un notebook ne modifiera pas votre historique git.

Bien que cela puisse probablement être adapté à d'autres VCS, je sais que cela ne satisfait pas vos exigences (au moins l'agnosticité des VSC). Pourtant, il est parfait pour moi, et bien qu'il n'ait rien de particulièrement brillant, et que de nombreuses personnes l'utilisent probablement déjà, je n'ai pas trouvé d'instructions claires sur la façon de le mettre en œuvre en cherchant sur Google. Elle peut donc être utile à d'autres personnes.

  1. Enregistrer un fichier avec ce contenu quelque part (pour la suite, supposons que ~/bin/ipynb_output_filter.py )

  2. Rendez-le exécutable ( chmod +x ~/bin/ipynb_output_filter.py )

  3. Créer le fichier ~/.gitattributes avec le contenu suivant

    *.ipynb filter=dropoutput_ipynb

  4. Exécutez les commandes suivantes :

    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

C'est fait !

Limitations :

  • cela ne fonctionne qu'avec git
  • dans git, si vous êtes dans la branche somebranch et vous le faites git checkout otherbranch; git checkout somebranch vous vous attendez généralement à ce que l'arbre de travail reste inchangé. Ici, au contraire, vous aurez perdu la sortie et la numérotation des cellules des cahiers dont la source diffère entre les deux branches.
  • plus en général, la sortie n'est pas versionnée du tout, comme avec la solution de Gregory. Afin de ne pas les jeter à chaque fois que vous faites quelque chose impliquant un checkout, l'approche pourrait être modifiée en les stockant dans des fichiers séparés (mais remarquez qu'au moment où le code ci-dessus est exécuté, l'identifiant de commit n'est pas connu ! git commit notebook_file.ipynb bien que cela permettrait au moins de garder git diff notebook_file.ipynb sans déchets de base64).
  • Cela dit, si vous tirez du code (c'est-à-dire commis par quelqu'un d'autre qui n'utilise pas cette approche) qui contient une sortie, celle-ci est extraite normalement. Seule la sortie produite localement est perdue.

Ma solution reflète le fait que je n'aime pas personnellement garder le matériel généré versionné - remarquez que faire des fusions impliquant la sortie est presque garanti pour invalider la sortie. ou votre productivité ou les deux.

EDIT :

  • si vous adoptez la solution telle que je l'ai suggérée - c'est-à-dire globalement - vous aurez des difficultés au cas où pour un quelconque repo git vous veulent à la sortie de la version. Ainsi, si vous voulez désactiver le filtrage de sortie pour un dépôt git spécifique, créez simplement à l'intérieur un fichier .git/info/attributs avec

    **.ipynb filter=

comme contenu. De la même manière, il est possible de faire l'inverse : activer le filtrage uniquement pour un référentiel spécifique.

  • le code est maintenant maintenu dans sa propre git repo

  • si les instructions ci-dessus donnent lieu à des ImportErrors, essayez d'ajouter "ipython" devant le chemin du script :

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

EDIT : Mai 2016 (mise à jour février 2017) : il existe plusieurs alternatives à mon script - pour être complet, voici une liste de celles que je connais : nbstripout ( autre variantes ), nbstrip , jq .

2 votes

Comment gérez-vous la question de l'incorporation des changements que vous tirez ? Vous contentez-vous de devoir régénérer toute la production ? (Je pense que c'est une manifestation de votre deuxième limitation).

0 votes

J'espère avoir clarifié ce point maintenant !

0 votes

Il y a un nouveau petit problème avec mon approche : si vous faites une modification, sauvegardez le carnet, revenez sur cette modification et sauvegardez à nouveau, la "signature" peut avoir changé. Mais il est inutile de le montrer dans les différences. Ce problème, ainsi que celui de la "perte de la sortie", peut être résolu en ayant des fichiers temporaires cachés. C'est une approche plus complexe, mais je prévois de l'implémenter tôt ou tard.

64voto

Rich Signell Points 1751

Nous avons un projet de collaboration dont le produit est Jupyter Notebooks, et nous utilisons depuis six mois une approche qui fonctionne très bien : nous activons la sauvegarde des .py automatiquement et suivre les deux .ipynb et les fichiers .py des fichiers.

De cette façon, si quelqu'un veut voir/télécharger le dernier notebook, il peut le faire via github ou nbviewer, et si quelqu'un veut voir comment le code du notebook a changé, il peut simplement regarder les changements apportés à l'élément .py des fichiers.

Pour Jupyter serveurs d'ordinateurs portables cela peut être réalisé en ajoutant les lignes

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

au jupyter_notebook_config.py et redémarrer le serveur notebook.

Si vous n'êtes pas sûr dans quel répertoire trouver votre jupyter_notebook_config.py vous pouvez taper jupyter --config-dir et si vous ne trouvez pas le fichier là, vous pouvez le créer en tapant jupyter notebook --generate-config .

Pour Ipython 3 serveurs d'ordinateurs portables cela peut être réalisé en ajoutant les lignes

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

au ipython_notebook_config.py et redémarrer le serveur notebook. Ces lignes sont tirées d'une réponse à un problème sur Github. @minrk a fourni et @dror les inclut également dans sa réponse au SO.

Pour Ipython 2 serveurs d'ordinateurs portables Pour ce faire, il suffit de démarrer le serveur en utilisant :

ipython notebook --script

ou en ajoutant la ligne

c.FileNotebookManager.save_script = True

au ipython_notebook_config.py et redémarrer le serveur notebook.

Si vous n'êtes pas sûr dans quel répertoire trouver votre ipython_notebook_config.py vous pouvez taper ipython locate profile default et si vous ne trouvez pas le fichier à cet endroit, vous pouvez le créer en tapant ipython profile create .

Voici notre projet sur github qui utilise cette approche et voici un exemple github d'exploration des modifications récentes apportées à un carnet de notes .

Nous en sommes très satisfaits.

1 votes

Merci pour la preuve supplémentaire que l'utilisation --script a fonctionné dans la pratique. Le problème est que les carnets de notes peuvent être énormes si l'on conserve des images. Une solution idéale dans ce sens pourrait utiliser quelque chose comme git-annex pour ne garder la trace que du dernier cahier complet.

0 votes

Dans Ipython 3.x, l'option --script est déprécié. ipython.org/ipython-doc/3/whatsnew/version3.html

0 votes

Merci @dror, j'ai mis à jour ma réponse pour fournir la solution ipython 3.x de minrk comme vous l'avez également fournie ici.

40voto

kynan Points 2334

J'ai créé nbstripout sur la base de Liste des MinRKs qui supporte à la fois Git et Mercurial (merci à mforbes). Il est destiné à être utilisé soit de manière autonome sur la ligne de commande, soit comme un filtre, qui est facilement (dé)installé dans le dépôt actuel par l'intermédiaire de nbstripout install / nbstripout uninstall .

Obtenez-le de PyPI ou simplement

pip install nbstripout

29voto

nik Points 546

Comme il existe de nombreuses stratégies et outils pour gérer le contrôle de version des carnets de notes, j'ai essayé de créer un diagramme de flux pour choisir une stratégie appropriée (créé en avril 2019).

Decision flow to pick version control strategy

23voto

SwimBikeRun Points 1395

Les réponses très populaires de 2016 ci-dessus sont des bidouillages inconsistants par rapport à la meilleure façon de faire en 2019.

Plusieurs options existent, la meilleure qui répond à la question est Jupytext.

Jupytext

Attrapez le Article "Towards Data Science" sur Jupytext

La façon dont cela fonctionne avec le contrôle de version est que vous mettez les deux fichiers .py et .ipynb dans le contrôle de version. Regardez le fichier .py si vous voulez le différentiel d'entrée, regardez le fichier .ipynb si vous voulez le dernier rendu.

Mentions notables : VS studio, nbconvert, nbdime, hydrogen

Je pense qu'avec un peu plus de travail, VS studio et/ou hydrogen (ou similaire) deviendront les acteurs dominants de la solution à ce flux de travail.

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