Używanie notesów IPython pod kontrolą wersji


569

Jaka jest dobra strategia utrzymywania notebooków IPython pod kontrolą wersji?

Format notebooka jest całkiem podatny na kontrolę wersji: jeśli ktoś chce kontrolować wersję notebooka i wyjść, działa to całkiem dobrze. Drażliwość pojawia się, gdy chce się tylko kontrolować wersję danych wejściowych, wyłączając wyjścia komórek (inaczej „produkty do budowania”), które mogą być dużymi binarnymi plamami, szczególnie dla filmów i fabuł. W szczególności staram się znaleźć dobry przepływ pracy, który:

  • pozwala mi wybierać między włączeniem lub wyłączeniem danych wyjściowych,
  • zapobiega przypadkowemu uruchomieniu wyjścia, jeśli go nie chcę,
  • pozwala mi zachować dane wyjściowe w mojej lokalnej wersji,
  • pozwala mi zobaczyć, kiedy mam zmiany danych wejściowych za pomocą mojego systemu kontroli wersji (tj. jeśli tylko kontroluję wersję danych wejściowych, ale mój plik lokalny ma dane wyjściowe, to chciałbym móc zobaczyć, czy dane wejściowe uległy zmianie (wymaga zatwierdzenia Użycie polecenia statusu kontroli wersji zawsze rejestruje różnicę, ponieważ plik lokalny ma dane wyjściowe.)
  • pozwala mi aktualizować mój działający notatnik (który zawiera dane wyjściowe) z zaktualizowanego czystego notatnika. (aktualizacja)

Jak wspomniano, jeśli zdecyduję się na dołączenie wyjść (co jest pożądane, na przykład przy użyciu nbviewer ), wtedy wszystko będzie dobrze. Problem polega na tym, że nie chcę kontrolować wersji danych wyjściowych. Istnieją narzędzia i skrypty do usuwania danych wyjściowych z notatnika, ale często napotykam następujące problemy:

  1. Przypadkowo zatwierdzam wersję z wyjściem, zanieczyszczając w ten sposób moje repozytorium.
  2. Wyczyszczam dane wyjściowe, aby użyć kontroli wersji, ale tak naprawdę wolałbym zachować dane wyjściowe w mojej lokalnej kopii (czasami na przykład odtworzenie zajmuje trochę czasu).
  3. Niektóre skrypty usuwające dane wyjściowe nieznacznie zmieniają format w porównaniu z Cell/All Output/Clearopcją menu, tworząc w ten sposób niepożądany szum w różnicach. Rozwiązują to niektóre odpowiedzi.
  4. Wyciągając zmiany do czystej wersji pliku, muszę znaleźć sposób na wprowadzenie tych zmian do mojego roboczego notebooka bez konieczności ponownego uruchamiania wszystkiego. (aktualizacja)

Rozważyłem kilka opcji, które omówię poniżej, ale jeszcze nie znalazłem dobrego kompleksowego rozwiązania. Pełne rozwiązanie może wymagać pewnych zmian w IPython lub może polegać na prostych skryptach zewnętrznych. Obecnie używam rtęci , ale chciałbym rozwiązania, które również działa z git : idealnym rozwiązaniem byłoby agnostyk kontroli wersji.

Zagadnienie to było omawiane wiele razy, ale nie ma ostatecznego ani jasnego rozwiązania z perspektywy użytkownika. Odpowiedź na to pytanie powinna stanowić ostateczną strategię. Jest w porządku, jeśli wymaga najnowszej (nawet programistycznej) wersji IPython lub łatwo instalowanego rozszerzenia.

Aktualizacja: gram z moją zmodyfikowaną wersją notebooka, która opcjonalnie zapisuje .cleanwersję przy każdym zapisie, korzystając z sugestii Gregory Crosswhite . Spełnia to większość moich ograniczeń, ale pozostawia następujące nierozwiązane:

  1. Nie jest to jeszcze standardowe rozwiązanie (wymaga modyfikacji źródła ipython. Czy istnieje sposób na osiągnięcie tego zachowania za pomocą prostego rozszerzenia? Potrzebuje pewnego rodzaju zaczepu przy zapisie.
  2. Problemem z bieżącym przepływem pracy jest pobieranie zmian. Będą one wchodzić do .cleanpliku, a następnie muszą być jakoś zintegrowane z moją działającą wersją. (Oczywiście zawsze mogę ponownie uruchomić notatnik, ale może to być uciążliwe, szczególnie jeśli niektóre wyniki zależą od długich obliczeń, obliczeń równoległych itp.) Nie mam jeszcze pojęcia, jak rozwiązać ten problem . Być może przepływ pracy z rozszerzeniem takim jak ipycache może działać, ale wydaje się to trochę zbyt skomplikowane.

Notatki

Usuwanie (usuwanie) danych wyjściowych

  • Gdy notebook jest uruchomiony, można użyć Cell/All Output/Clearopcji menu do usunięcia danych wyjściowych.
  • Istnieją skrypty do usuwania danych wyjściowych, takie jak skrypt nbstripout.py, które usuwają dane wyjściowe, ale nie generują takich samych danych wyjściowych, jak przy użyciu interfejsu notebooka. Zostało to ostatecznie uwzględnione w repozytorium ipython / nbconvert , ale zostało to zamknięte, stwierdzając, że zmiany są teraz zawarte w ipython / ipython , ale odpowiednia funkcjonalność wydaje się, że nie została jeszcze uwzględniona. (aktualizacja) To powiedziawszy, rozwiązanie Gregory Crosswhite pokazuje, że jest to dość łatwe, nawet bez wywoływania ipython / nbconvert, więc to podejście jest prawdopodobnie wykonalne, jeśli można je właściwie podłączyć. (Dołączenie go do każdego systemu kontroli wersji nie wydaje się jednak dobrym pomysłem - powinno to jakoś podłączyć się do mechanizmu notebooka).

Grupy dyskusyjne

Problemy

Wyciągnij wnioski


Brzmi jak świetna rzecz, którą można dodać jako problem na github.com/ipython/ipython lub przesłać żądanie ściągnięcia, które pomoże ci osiągnąć ten cel.
Kyle Kelley,

4
Gdy masz już działający skrypt do usuwania danych wyjściowych, możesz użyć filtra „czystego” Git, aby zastosować go automatycznie przed zatwierdzeniem (zobacz filtry czyszczenia / rozmazywania).
Matthias,

1
@foobarbecue Pytanie zawiera niezadowalające obejścia: każde ma co najmniej jedno ograniczenie. Teraz, gdy PR 4175 został scalony, prawdopodobnie można sformułować kompletne rozwiązanie, ale nadal należy to zrobić. Jak tylko będę miał trochę czasu, zrobię to (w odpowiedzi), jeśli w międzyczasie ktoś inny nie zapewni zadowalającego rozwiązania.
mforbes,

1
@saroele Nie znalazłem jeszcze zalecanego rozwiązania: zamierzałem wybrać --scriptopcję, ale została ona usunięta. Czekam na wdrożenie (po planowaniu ) haczyków po zapisaniu, w którym momencie myślę, że będę w stanie zapewnić akceptowalne rozwiązanie łączące kilka technik.
mforbes,

1
@mforbes Wygląda na to, że PR został scalony kilka dni po twoim komentarzu. Czy ty lub ktoś bardziej kompetentny ode mnie możesz tutaj zamieścić odpowiedź, która pokazuje, jak korzystać z nowej funkcji?
KobeJohn,

Odpowiedzi:


124

Oto moje rozwiązanie z git. Pozwala na zwykłe dodawanie i zatwierdzanie (i różnicowanie) w zwykły sposób: te operacje nie zmienią twojego drzewa roboczego, a jednocześnie (ponowne) uruchomienie notebooka nie zmieni twojej historii git.

Chociaż można to prawdopodobnie zaadaptować do innych VCS, wiem, że nie spełnia twoich wymagań (przynajmniej agnostyczność VSC). Mimo to jest dla mnie idealny i chociaż nie jest to nic szczególnie błyskotliwego, a wiele osób prawdopodobnie już go używa, nie znalazłem jasnych instrukcji, jak go wdrożyć, przeglądając go w Internecie. Może to być przydatne dla innych ludzi.

  1. Zapisz plik z tej zawartości gdzieś (na następny, załóżmy ~/bin/ipynb_output_filter.py)
  2. Spraw, aby był wykonywalny ( chmod +x ~/bin/ipynb_output_filter.py)
  3. Utwórz plik ~/.gitattributeso następującej treści

    *.ipynb    filter=dropoutput_ipynb
    
  4. Uruchom następujące polecenia:

    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
    

Gotowy!

Ograniczenia:

  • działa tylko z git
  • w git, jeśli jesteś w gałęzi somebranchi robisz to git checkout otherbranch; git checkout somebranch, zwykle oczekujesz, że działające drzewo pozostanie niezmienione. Zamiast tego utracisz dane wyjściowe i numerację komórek w notesach, których źródło różni się między dwiema gałęziami.
  • bardziej ogólnie, dane wyjściowe wcale nie są wersjonowane, jak w przypadku rozwiązania Gregory'ego. Aby nie tylko wyrzucić go za każdym razem, gdy robisz coś związanego z kasą, podejście można zmienić, przechowując go w osobnych plikach (ale zauważ, że w momencie uruchomienia powyższego kodu identyfikator zatwierdzenia nie jest znany!), i ewentualnie ich wersjonowanie (ale zauważ, że wymagałoby to czegoś więcej niż jednego git commit notebook_file.ipynb, chociaż przynajmniej utrzymałoby się git diff notebook_file.ipynbwolne od śmieci base64).
  • powiedziawszy, nawiasem mówiąc, jeśli ściągniesz kod (tj. popełniony przez kogoś innego, kto nie stosuje tego podejścia), który zawiera dane wyjściowe, dane wyjściowe są sprawdzane normalnie. Utracona jest tylko produkcja lokalnie produkowana.

Moje rozwiązanie odzwierciedla fakt, że osobiście nie lubię utrzymywać wersji wygenerowanych plików - zauważ, że wykonywanie połączeń z danymi wyjściowymi jest prawie gwarantowane, aby unieważnić dane wyjściowe lub wydajność lub jedno i drugie.

EDYTOWAĆ:

  • jeśli zastosujesz rozwiązanie tak, jak to zasugerowałem - to znaczy globalnie - będziesz miał kłopoty na wypadek, gdyby jakieś repozytorium git chciało zaktualizować dane wyjściowe. Więc jeśli chcesz wyłączyć filtrowanie danych wyjściowych dla określonego repozytorium git, po prostu stwórz w nim plik .git / info / attribute , z

    **. Filtr ipynb =

jako treść. Oczywiście w ten sam sposób można zrobić odwrotnie: włącz filtrowanie tylko dla określonego repozytorium.

  • kod jest teraz utrzymywany we własnym repozytorium git

  • jeśli powyższe instrukcje prowadzą do ImportErrors, spróbuj dodać „ipython” przed ścieżką skryptu:

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

EDYCJA : maj 2016 (zaktualizowany luty 2017): istnieje kilka alternatyw dla mojego skryptu - dla kompletności, oto lista tych, które znam: nbstripout ( inne warianty ), nbstrip , jq .


2
Jak sobie radzisz z wprowadzaniem wprowadzanych zmian? Czy po prostu żyjesz z koniecznością regeneracji całej mocy? (Myślę, że jest to przejaw twojego drugiego ograniczenia).
mforbes

1
@zhermes: ta rozszerzona wersja powinna być OK
Pietro Battiston

1
Czy istnieje sposób na użycie tej metody filtrów git z zewnętrznym narzędziem do porównywania? Filtr jest stosowany, jeśli korzystam z normalnego narzędzia wiersza poleceń, ale nie, jeśli używam meldowania jako narzędzia różnicowego. stackoverflow.com/q/30329615/578770
FA

1
Aby uniknąć dostania się ImportError, musiałem zmienić powyższe, aby uruchomić za pomocą ipython:git config --global filter.dropoutput_ipynb.clean ipython ~/bin/ipynb_output_filter.py
chris838

1
Niesamowite rozwiązanie Pietro, dzięki :) Zmieniłem 2 rzeczy podczas używania twojego skryptu w moim przypadku: 1) Wolałem zadeklarować filtr w .gitattributes w katalogu głównym repo, w przeciwieństwie do ~/.gitattributesinnych osób, które mają takie same filtry jak ja 2 ) Zdefiniowałem workdir/**/*.ipynb filter=dropoutput_ipynbwyrażenie regularne jako i umieszczam większość moich notatników w workdir / => jeśli nadal chcę wypchnąć notebook z wyjściem i cieszyć się imponującym renderowaniem w github, po prostu umieszczam go poza tym folderem.
Svend

63

Mamy wspólny projekt, w którym produktem są notesy Jupyter, i stosujemy podejście od sześciu miesięcy, które działa świetnie: aktywujemy zapisywanie .pyplików automatycznie i śledzimy zarówno .ipynbpliki, jak i .pypliki.

W ten sposób, jeśli ktoś chce wyświetlić / pobrać najnowszy notatnik, może to zrobić za pomocą github lub nbviewer, a jeśli ktoś chce zobaczyć, jak zmienił się kod notatnika, może po prostu spojrzeć na zmiany w .pyplikach.

W przypadku Jupyterserwerów notebooków można to osiągnąć przez dodanie wierszy

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

do jupyter_notebook_config.pypliku i restartowanie serwera notebooka.

Jeśli nie masz pewności, w którym katalogu znaleźć jupyter_notebook_config.pyplik, możesz wpisać jupyter --config-dir, a jeśli go nie znajdziesz, możesz go utworzyć, pisząc jupyter notebook --generate-config.

W przypadku Ipython 3serwerów notebooków można to osiągnąć przez dodanie wierszy

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

do ipython_notebook_config.pypliku i restartowanie serwera notebooka. Te wiersze pochodzą od dostarczonych przez gituba problemów z odpowiedzią @minrk, a @dror również je zamieszcza w swojej odpowiedzi SO.

W przypadku Ipython 2serwerów notebooków można to zrobić, uruchamiając serwer za pomocą:

ipython notebook --script

lub dodając linię

c.FileNotebookManager.save_script = True

do ipython_notebook_config.pypliku i restartowanie serwera notebooka.

Jeśli nie masz pewności, w którym katalogu znaleźć ipython_notebook_config.pyplik, możesz wpisać ipython locate profile default, a jeśli go nie znajdziesz, możesz go utworzyć, pisząc ipython profile create.

Oto nasz projekt dotyczący github, który wykorzystuje to podejście : i oto przykład github dotyczący odkrywania ostatnich zmian w notatniku .

Jesteśmy z tego bardzo zadowoleni.


1
Dzięki za dodatkowe dowody, że używanie --scriptdziałało w praktyce. Problem polega na tym, że faktyczne notebooki mogą być ogromne, jeśli przechowywane są obrazy. Idealnym rozwiązaniem na tej drodze może być coś takiego jak git-annex, aby śledzić tylko najnowszy pełny notebook.
mforbes,

W Ipython 3.x --scriptjest przestarzały. ipython.org/ipython-doc/3/whatsnew/version3.html
Dror

Dzięki @dror, zaktualizowałem swoją odpowiedź, aby zapewnić rozwiązanie iprit 3.x Minrka, tak jak tutaj.
Rich Signell,

10
Aktualizacja: To rozwiązanie jest zepsute w iPython w wersji 4 z powodu „The Big Split” Jupytera z iPython. Aby dostosować to rozwiązanie do wersji 4, użyj polecenia, jupyter notebook --generate-configaby utworzyć plik konfiguracyjny. Polecenie jupyter --config-dirsprawdza, który katalog zawiera pliki konfiguracyjne. Fragment kodu podany przez @Rich powinien zostać dodany do pliku o nazwie jupyter_notebook_config.py. Reszta działa jak poprzednio.
mobius dumpling

2
Oprócz punktu przez @mobiusdumpling, wymienić check_call(['ipython'się check_call(['jupyter', inaczej otrzymasz ostrzeżenie, że ipython nbconvertjest nieaktualna i należy użyć jupyter nbconvertzamiast. (Jupyter v4.1.0, iPython v4.1.2)
cutculus

36

Stworzyłem nbstripout, w oparciu o gist MinRK , który obsługuje zarówno Git, jak i Mercurial (dzięki mforbes). Jest przeznaczony do użycia jako samodzielny w wierszu poleceń lub jako filtr, który można łatwo (nie) zainstalować w bieżącym repozytorium przez nbstripout install/ nbstripout uninstall.

Zdobądź go z PyPI lub po prostu

pip install nbstripout

Rozważam przepływ pracy, w którym przechowuję zarówno plik .ipynb, jak i odpowiedni plik .py automatycznie utworzone przy użyciu haków po zapisaniu opisanych powyżej. Chciałbym użyć .py dla diffs - czy nbstripout byłby w stanie wyczyścić plik .py z liczników wykonania komórki (# In [1] zmieniono na In [*]), aby nie zaśmiecały różnic ani nie powinienem stworzyć prosty skrypt do zrobienia tego?
Krzysztof Słowiński,

1
@ KrzysztofSłowiński Nie, nbstripoutnie obsługuje łatwo tego przypadku użycia, ponieważ opiera się on na formacie JSON notatnika. Prawdopodobnie lepiej jest napisać skrypt specjalizujący się w twoim przypadku użycia.
kynan


13

Po kilku latach usuwania danych wyjściowych w notebookach próbowałem znaleźć lepsze rozwiązanie. Teraz używam Jupytext , rozszerzenia zarówno dla Jupyter Notebook, jak i Jupyter Lab, które zaprojektowałem.

Jupytext może konwertować notesy Jupyter na różne formaty tekstowe (Skrypty, Markdown i R Markdown). I odwrotnie. Oferuje również opcję sparowania notebooka z jednym z tych formatów i automatycznej synchronizacji dwóch reprezentacji notebooka (an .ipynbi .md/.py/.Rpliku).

Pozwól mi wyjaśnić, w jaki sposób Jupytext odpowiada na powyższe pytania:

pozwala mi wybierać między włączeniem lub wyłączeniem danych wyjściowych,

.md/.py/.RPlik zawiera tylko komórki wejściowych. Zawsze powinieneś śledzić ten plik. Wersja .ipynbpliku tylko, jeśli chcesz śledzić wyniki.

zapobiega przypadkowemu uruchomieniu wyjścia, jeśli go nie chcę,

Dodaj *.ipynbdo.gitignore

pozwala mi zachować dane wyjściowe w mojej lokalnej wersji,

Dane wyjściowe są przechowywane w .ipynbpliku (lokalnym)

pozwala mi zobaczyć, kiedy mam zmiany danych wejściowych za pomocą mojego systemu kontroli wersji (tj. jeśli tylko kontroluję wersję danych wejściowych, ale mój plik lokalny ma dane wyjściowe, to chciałbym móc zobaczyć, czy dane wejściowe uległy zmianie (wymaga zatwierdzenia Użycie polecenia statusu kontroli wersji zawsze rejestruje różnicę, ponieważ plik lokalny ma dane wyjściowe.)

Różnica w pliku .py/.Rlub .mdjest tym, czego szukasz

pozwala mi aktualizować mój działający notatnik (który zawiera dane wyjściowe) z zaktualizowanego czystego notatnika. (aktualizacja)

Pobierz najnowszą wersję pliku .py/.Rlub .mdi odśwież swój notatnik w Jupyter (Ctrl + R). Otrzymasz najnowsze komórki wejściowe z pliku tekstowego, z pasującymi danymi wyjściowymi z .ipynbpliku. Nie ma to wpływu na jądro, co oznacza, że ​​zmienne lokalne są zachowane - możesz kontynuować pracę tam, gdzie ją zostawiłeś.

W Jupytext uwielbiam to, że notatnik (w postaci pliku .py/.Rlub .md) można edytować w swoim ulubionym środowisku IDE. Dzięki takiemu podejściu refaktoryzacja notebooka staje się łatwa. Po zakończeniu wystarczy odświeżyć notatnik w Jupyter.

Jeśli chcesz spróbować: zainstaluj Jupytext za pomocą pip install jupytexti uruchom ponownie edytor Jupyter Notebook lub Lab. Otwórz notatnik, który chcesz kontrolować wersję, i sparuj go z plikiem Markdown (lub skryptem) za pomocą menu Jupytext w notatniku Jupyter (lub poleceń Jupytext w Jupyter Lab). Zapisz swój notatnik, a otrzymasz dwa pliki: oryginał .ipynboraz obiecaną reprezentację tekstową notatnika, co idealnie pasuje do kontroli wersji!

Dla zainteresowanych: Jupytext jest także dostępny w linii poleceń .


13

Aktualizacja : teraz możesz edytować pliki notesu Jupyter bezpośrednio w programie Visual Studio Code. Możesz wybrać edycję notatnika lub przekonwertowanego pliku python.

W końcu znalazłem produktywny i prosty sposób, aby Jupyter i Git dobrze się ze sobą bawili. Wciąż jestem w pierwszych krokach, ale już myślę, że jest o wiele lepszy niż wszystkie inne skomplikowane rozwiązania.

Visual Studio Code to fajny i otwarty edytor kodu firmy Microsoft. Ma doskonałe rozszerzenie Python, które pozwala teraz importować Notatnik Jupyter jako kod Pythona. Teraz możesz także bezpośrednio edytować notesy Jupyter .

Po zaimportowaniu notebooka do pliku python cały kod i znaczniki będą razem w zwykłym pliku python ze specjalnymi znacznikami w komentarzach. Możesz zobaczyć na obrazku poniżej:

Edytor VSCode z notatnikiem przekonwertowanym na python

Twój plik python ma po prostu zawartość komórek wejściowych notebooka. Dane wyjściowe zostaną wygenerowane w podzielonym oknie. Masz czysty kod w notatniku, nie zmienia się on podczas jego wykonywania. Brak mieszanego wyniku z kodem. Żaden dziwny niezrozumiały format JSON do analizy twoich różnic.

Po prostu czysty kod Pythona, w którym można łatwo zidentyfikować każdą różnicę.

Nie muszę już nawet aktualizować swoich .ipynbplików. Mogę wstawić *.ipynblinię .gitignore.

Chcesz wygenerować notatnik, aby go opublikować lub udostępnić komuś? Nie ma problemu, wystarczy kliknąć przycisk eksportu w interaktywnym oknie pythona

Eksportowanie pliku python do formatu Notatnik

Jeśli edytujesz notatnik bezpośrednio, teraz jest ikona Convert and save to a python script. Ikony Jupyter w programie Visual Studio Code

Oto zrzut ekranu notebooka w programie Visual Studio Code:

Edycja Notatnika w VSCode

Używam go tylko przez jeden dzień, ale w końcu mogę z radością korzystać z Jupyter z Git.

PS: Uzupełnianie kodu VSCode jest o wiele lepsze niż Jupyter.


12

(2017-02)

strategie

  • on_commit ():
    • usuń dane wyjściowe> name.ipynb ( nbstripout,)
    • usuń dane wyjściowe> name.clean.ipynb ( nbstripout,)
    • zawsze nbconvertpython: name.ipynb.py ( nbconvert)
    • zawsze konwertuj na markdown: name.ipynb.md ( nbconvert, ipymd)
  • vcs.configure ():
    • git difftool, scaletool: nbdiff i nbmerge z nbdime

przybory


11

Bardzo popularne powyższe odpowiedzi na 2016 rok to niespójne hacki w porównaniu z lepszym sposobem na zrobienie tego w 2019 roku.

Istnieje kilka opcji, najlepsza odpowiedź na pytanie to Jupytext.

Jupytext

Złapać Ku Data Science artykuł na Jupytext

Działa z kontrolą wersji w taki sposób, że umieszczasz zarówno pliki .py, jak i .ipynb w kontroli wersji. Spójrz na plik .py, jeśli chcesz różnicę wejściową, spójrz na plik .ipynb, jeśli chcesz mieć ostatni renderowany wynik.

Godne uwagi wspomnienia: VS studio, nbconvert, nbdime, wodór

Myślę, że przy odrobinie pracy, VS studio i / lub wodór (lub podobny) staną się dominującymi graczami w rozwiązaniu tego przepływu pracy.


9

Wystarczy natknąć się na „jupytext”, który wygląda jak idealne rozwiązanie. Generuje plik .py z notebooka, a następnie synchronizuje oba pliki. Możesz kontrolować wersję, różnicować i scalać dane wejściowe za pomocą pliku .py bez utraty danych wyjściowych. Po otwarciu notesu używa .py dla komórek wejściowych i .ipynb dla danych wyjściowych. A jeśli chcesz dołączyć dane wyjściowe do git, możesz po prostu dodać ipynb.

https://github.com/mwouts/jupytext


9

Ponieważ istnieje tak wiele strategii i narzędzi do obsługi kontroli wersji dla notebooków, próbowałem utworzyć schemat blokowy, aby wybrać odpowiednią strategię (utworzono w kwietniu 2019 r.)

Przepływ decyzji w celu wybrania strategii kontroli wersji


8

Jak wskazano, --scriptjest przestarzałe w 3.x. Tego podejścia można użyć, stosując hak po zapisaniu. W szczególności dodaj następujące elementy do 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

Kod pochodzi z numeru # 8009 .


Dziękujemy za zademonstrowanie użycia haka po zapisaniu. Niestety, jak wspomniano wcześniej, powrót z .pypliku do notebooka jest problematyczny, więc nie jest to niestety kompletne rozwiązanie. (Chciałbym, żeby było tak, ponieważ bardzo fajnie jest różnicować .pypliki zamiast notebooków. Być może nowa funkcja różnicowania notebooków będzie przydatna.
mforbes

1
Dzięki! Teraz używam tej sztuczki do odtworzenia --scriptzachowania, niezależnie od kontroli wersji. Na początku miałem pewne problemy, więc na wszelki wypadek mogę komuś zaoszczędzić trochę czasu: 1) Jeśli ipython_notebook_config.pybrakuje w folderze profilu, uruchom go, ipython profile createaby go wygenerować. 2) Jeśli wydaje się, że hak po zapisaniu jest ignorowany, uruchom ipython z, --debugaby zdiagnozować problem. 3) Jeśli skrypt nie powiedzie się z powodu błędu ImportError: No module named mistune- prosta instalacja minstue: pip install mistune.
Joe

7

Niestety, niewiele wiem o Mercurial, ale mogę dać ci możliwe rozwiązanie, które działa z Git, w nadziei, że możesz być w stanie przetłumaczyć moje polecenia Git na ich odpowiedniki Mercurial.

W tle w Git addpolecenie przechowuje zmiany wprowadzone w pliku w obszarze przejściowym. Gdy to zrobisz, wszelkie późniejsze zmiany w pliku są ignorowane przez Git, chyba że powiesz mu, aby je również wystawił. W związku z tym następujący skrypt, który dla każdego z podanych plików usuwa wszystkie outputsi prompt_number sectionsustawia etap usuwania pliku, a następnie przywraca oryginał:

UWAGA: Jeśli po uruchomieniu pojawi się komunikat o błędzie ImportError: No module named IPython.nbformat, użyj przycisku, ipythonaby uruchomić skrypt zamiast 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)

Po uruchomieniu skryptu na plikach, których zmiany chcesz zatwierdzić, po prostu uruchom git commit.


Dzieki za sugestie. Mercurial tak naprawdę nie ma obszaru przejściowego takiego jak git (chociaż można do tego celu użyć kolejek rtęciowych ). W międzyczasie próbowałem dodać ten kod do haka składowania, który zapisuje czystą wersję z .cleanrozszerzeniem. Niestety nie widziałem, jak to zrobić bez bezpośredniej modyfikacji IPython (chociaż ta zmiana była dość trywialna). Będę się z tym bawić przez chwilę i sprawdzę, czy odpowiada to wszystkim moim potrzebom.
mforbes,

6

Używam bardzo pragmatycznego podejścia; które działają dobrze dla kilku notebooków, z kilku stron. Pozwala mi nawet „przenosić” zeszyty. Działa zarówno w systemie Windows jak Unix / MacOS.
Al myślał, że to proste, rozwiązuje powyższe problemy ...

Pojęcie

Zasadniczo nie śledź .ipnyb-plików, tylko odpowiadające im .pypliki.
Po uruchomieniu serwera notebooka z --scriptopcją ten plik jest automatycznie tworzony / zapisywany podczas zapisywania notebooka.

Te .pypliki zawierają wszystkie dane wejściowe; non-code jest zapisywany w komentarzach, podobnie jak granice komórek. Te pliki można odczytać / zaimportować (i przeciągnąć) na serwer notebooka, aby (ponownie) utworzyć notes. Nie ma już wyjścia; dopóki nie zostanie ponownie uruchomiony.

Osobiście używam mercurial do śledzenia wersji .pyplików; i użyj zwykłych poleceń (wiersza poleceń), aby dodać, zameldować się (ect). Na to pozwala większość innych (D) VCS.

Teraz łatwo jest śledzić historię; .pysą małe, tekstowych i prosty diff. Raz na jakiś czas potrzebujemy klona (po prostu oddziału; uruchom tam drugi notebook-sever) lub starszej wersji (sprawdź i zaimportuj na serwer notebooka) itp.

Porady & Triki

  • Dodaj * .ipynb do ' .hgignore ', aby Mercurial wiedział, że może zignorować te pliki
  • Utwórz skrypt (bash), aby uruchomić serwer (z --scriptopcją) i wykonaj śledzenie wersji
  • Zapisanie notesu zapisuje .pyplik -f, ale go nie rejestruje .
    • Jest to wada : można o tym zapomnieć
    • Jest to także funkcja : możliwe jest zapisanie notatnika (i kontynuacja później) bez grupowania historii repozytorium.

Życzenia

  • Byłoby miło mieć przyciski do odprawy / dodawania / etc w desce rozdzielczej notebooka
  • file@date+rev.pyPomocne przy kasie (na przykład) ) powinno być dodanie pracy; i może zrobię to raz. Do tej pory robię to ręcznie.

Jak wrócić z .pypliku do notebooka? Lubię to podejście, ale ponieważ .ipynb-> .py-> .ipynbjest potencjalnie stratny, nie wziąłem tego za poważnie.
mforbes

To proste: załaduj go, na przykład upuszczając go na desce rozdzielczej notebooka. Z wyjątkiem „danych wyjściowych” nic nie jest tracone
Albert

Jeśli to prawda, to myślę, że byłoby to bliskie idei, ale wydaje mi się, że pamiętam, że IPython nie zobowiązał się do całkowitej ochrony danych podczas przechodzenia z formatów .pyna .ipynbformat. Jest w tym problem - być może będzie to stanowić podstawę do kompletnego rozwiązania.
mforbes

Mam trudności z konwersją .pyplików do .ipynbplików. nbconvertwydaje się jeszcze nie obsługiwać tego i nie mam pulpitu nawigacyjnego notebooka, ponieważ uruchamiam go ipython notebookręcznie. Czy masz jakieś ogólne sugestie dotyczące sposobu wdrożenia tej konwersji wstecznej?
mforbes,

Z pewnością .pytransformacja z notebooka nie ma na celu podróży w obie strony. Więc to nie może być ogólne rozwiązanie, chociaż fajnie, że działa dla ciebie.
holdenweb

3

Aby kontynuować doskonały skrypt Pietro Battistona, jeśli wystąpi błąd analizowania Unicode, taki jak ten:

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)

Możesz dodać na początku skryptu:

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

3

Zbudowałem pakiet python, który rozwiązuje ten problem

https://github.com/brookisme/gitnb

Zapewnia CLI z inspirowaną gitem składnią do śledzenia / aktualizacji / różnicowania notebooków wewnątrz repozytorium git.

Oto przykład

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

Zwróć uwagę, że ostatnim krokiem, w którym używam polecenia „gitnb commit”, jest zatwierdzenie repozytorium git. Jest to zasadniczo opakowanie dla

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

Istnieje kilka innych metod, które można skonfigurować tak, aby wymagały mniej lub więcej wkładu użytkownika na każdym etapie, ale taki jest ogólny pomysł.


3

Po przekopaniu się, w końcu znalazłem ten stosunkowo prosty haczyk przed zapisaniem w dokumentacji Jupytera . Usuwa dane wyjściowe komórki. Musisz wkleić go do jupyter_notebook_config.pypliku (instrukcje poniżej).

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

Z odpowiedzi Richa Signella :

Jeśli nie masz pewności, w którym katalogu znaleźć jupyter_notebook_config.pyplik, możesz wpisać jupyter --config-dir[w wierszu polecenia / terminalu], a jeśli nie znajdziesz tam pliku, możesz go utworzyć, wpisując jupyter notebook --generate-config.


1
Chciałbym zauważyć, że to rozwiązanie nigdy nie zapisuje żadnych danych wyjściowych na dysku i jest nieco niezależne od problemu kontroli wersji.
bdforbes

2

Zrobiłem to, co zrobili Albert & Rich - nie wersjonuj plików .ipynb (ponieważ mogą one zawierać obrazy, co robi się bałagan). Zamiast tego albo zawsze uruchom ipython notebook --scriptlub włóż c.FileNotebookManager.save_script = Trueplik konfiguracyjny, aby (wersja).py był zawsze tworzony podczas zapisywania notebooka.

Aby zregenerować notebooki (po sprawdzeniu repozytorium lub zmianie gałęzi) wstawiam skrypt py_file_to_notebooks.py w katalogu, w którym przechowuję swoje notebooki.

Teraz, po sprawdzeniu repozytorium, po prostu uruchom, python py_file_to_notebooks.pyaby wygenerować pliki ipynb. Po zmianie gałęzi może być konieczne uruchomieniepython py_file_to_notebooks.py -ov celu zastąpienia istniejących plików ipynb.

Aby być bezpiecznym, dobrze jest również dodać *.ipynbdo swojego.gitignore pliku.

Edycja: Już tego nie robię, ponieważ (A) musisz regenerować swoje notesy z plików py za każdym razem, gdy kasujesz gałąź, i (B) istnieją inne rzeczy, takie jak obniżanie cen w notatnikach, które tracisz. Zamiast tego usuwam dane wyjściowe z notebooków za pomocą filtra git. Dyskusja na temat tego, jak to zrobić, znajduje się tutaj .


Podobał mi się ten pomysł, ale po przetestowaniu okazało się, że konwersja .pyplików z powrotem .ipynbjest problematyczna, szczególnie w przypadku notebooków w wersji 4, dla których nie ma jeszcze konwertera. W tej chwili należałoby użyć importera v3, a następnie przekonwertować na v4 i jestem trochę zaniepokojony tą skomplikowaną podróżą. Ponadto, .pyplik nie jest to bardzo dobry wybór, jeśli notebook jest przede wszystkim kod Julia! Wreszcie --scriptjest przestarzałe, więc myślę, że haczyki są dobrym rozwiązaniem.
mforbes

Rozwiązanie filtru git w twoim linku jest dobre, powinieneś skopiować stąd swoją odpowiedź :-)
mcarans

2

Ok, więc wygląda na to, że jest to obecnie najlepsze rozwiązanie, zgodnie z dyskusją tutaj , jest utworzenie filtra git, aby automatycznie usuwać dane wyjściowe z plików ipynb podczas zatwierdzania.

Oto, co zrobiłem, aby to działało (skopiowane z tej dyskusji):

Lekko zmodyfikowałem plik nbstripout cfriedline, aby dać błąd informacyjny, gdy nie możesz zaimportować najnowszego IPython: https://github.com/petered/plato/blob/fb2f4e252f50c79768920d0e47b870a8d799e92b/notebooks/config/strip_notebook_outputo i dodałem to do mojej odpowiedzi powiedzieć w./relative/path/to/strip_notebook_output

Dodano także plik .gitattributes do katalogu głównego repozytorium, zawierający:

*.ipynb filter=stripoutput

I stworzył setup_git_filters.shzawierający

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

I pobiegł source setup_git_filters.sh. Fantazyjne $ (git rev-parse ...) polega na znalezieniu lokalnej ścieżki repozytorium na dowolnej (uniksowej) maszynie.


1

To rozszerzenie jupyter umożliwia użytkownikom wypychanie notesów jupyter bezpośrednio do github.

Proszę spojrzeć tutaj

https://github.com/sat28/githubcommit


czy możesz wyjaśnić, co to robi? Podwojenie nie jest szczególnie jasne.
Alex Monras

@AlexMonras Spowoduje to bezpośrednie dodanie przycisku w notatniku jupyter, z którego można wypychać notebooki do repozytorium GitHub z komunikatem zatwierdzenia
sob

1

Jest kwiecień 2020 r. I istnieje wiele strategii i narzędzi do kontroli wersji notebooków Jupyter. Oto krótki przegląd wszystkich dostępnych narzędzi,

  • nbdime - Nicea do lokalnego różnicowania i łączenia zeszytów

  • nbstripout - Filtr git do automatycznego usuwania wyników z notebooka przed każdym zatwierdzeniem

  • jupytext - Utrzymuje plik towarzyszący .py zsynchronizowany z każdym notatnikiem. Zatwierdzasz tylko pliki .py

  • nbconvert - Konwertuj notebooki na skrypt Pythona lub HTML (lub oba) i zatwierdzaj te alternatywne typy plików

  • ReviewNB - Pokazuje różnicę w notatniku (wraz z danymi wyjściowymi) dla każdego żądania zatwierdzenia lub pobrania w GitHub. Można również pisać komentarze do komórek notebooka, aby omówić zmiany (zrzut ekranu poniżej).

wprowadź opis zdjęcia tutaj

Uwaga: Zbudowałem ReviewNB.


0

Co powiesz na pomysł omówiony w poniższym poście, w którym należy przechowywać dane wyjściowe notebooka, z argumentem, że wygenerowanie go może zająć dużo czasu, i jest to przydatne, ponieważ GitHub może teraz renderować notebooki. Dodano haki automatycznego zapisywania do eksportowania pliku .py, używanego do różnic i .html do udostępniania członkom zespołu, którzy nie używają notesów ani gita.

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.