version control - Usando notebooks IPython bajo control de versiones
version-control jupyter-notebook (15)
¿Cuál es una buena estrategia para mantener las notebooks IPython bajo control de versión?
El formato del portátil es bastante fácil de controlar: si uno quiere controlar la versión del portátil y las salidas, esto funciona bastante bien. La molestia se produce cuando uno solo quiere controlar la entrada de la versión, excluyendo las salidas de celda (también conocidas como "productos de compilación") que pueden ser grandes burbujas binarias, especialmente para películas y gráficos. En particular, estoy tratando de encontrar un buen flujo de trabajo que:
- me permite elegir entre incluir o excluir la salida,
- me impide cometer errores accidentalmente si no lo quiero,
- me permite mantener la salida en mi versión local,
- me permite ver cuándo tengo cambios en las entradas usando mi sistema de control de versiones (es decir, si solo controlo las entradas de la versión pero mi archivo local tiene salidas, me gustaría poder ver si las entradas han cambiado (se requiere confirmación) El uso del comando de estado de control de versión siempre registrará una diferencia ya que el archivo local tiene salidas.
- me permite actualizar mi cuaderno de trabajo (que contiene la salida) de un cuaderno limpio actualizado. (actualizar)
Como mencioné, si elijo incluir las salidas (lo cual es deseable cuando se usa nbviewer por ejemplo), entonces todo está bien. El problema es cuando no quiero que la versión controle la salida. Existen algunas herramientas y scripts para eliminar la salida del portátil, pero con frecuencia me encuentro con los siguientes problemas:
- Accedo accidentalmente a una versión con la salida, contaminando así mi repositorio.
- Borro la salida para usar el control de versiones, pero realmente prefiero mantener la salida en mi copia local (a veces toma un tiempo reproducirla, por ejemplo).
- Algunos de los scripts que eliminan la salida cambian el formato ligeramente en comparación con la opción de menú
Cell/All Output/Clear
, creando así ruidos no deseados en las diferencias. Esto se resuelve con algunas de las respuestas. - Al extraer cambios en una versión limpia del archivo, necesito encontrar alguna forma de incorporar esos cambios en mi cuaderno de trabajo sin tener que volver a ejecutar todo. (actualizar)
He considerado varias opciones que discutiré a continuación, pero aún tengo que encontrar una buena solución integral. Una solución completa puede requerir algunos cambios en IPython, o puede depender de algunos scripts externos simples. Actualmente utilizo mercurial , pero me gustaría una solución que también funcione con git : una solución ideal sería el agnóstico de control de versiones.
Este problema se ha discutido muchas veces, pero no hay una solución definitiva o clara desde la perspectiva del usuario. La respuesta a esta pregunta debe proporcionar la estrategia definitiva. Está bien si requiere una versión reciente (incluso de desarrollo) de IPython o una extensión de fácil instalación.
Actualización: He estado jugando con mi versión modificada de notebook , que opcionalmente guarda una versión .clean
con cada guardado usando las sugerencias de Gregory Crosswhite . Esto satisface la mayoría de mis restricciones, pero deja sin resolver lo siguiente:
- Esto aún no es una solución estándar (requiere una modificación de la fuente ipython. ¿Hay alguna forma de lograr este comportamiento con una extensión simple? ¿Necesita algún tipo de gancho para guardar?
- Un problema que tengo con el flujo de trabajo actual es sacar cambios. Estos entrarán en el archivo
.clean
, y luego deberán integrarse de alguna manera en mi versión de trabajo. (Por supuesto, siempre puedo volver a ejecutar el cuaderno, pero esto puede ser una molestia, especialmente si algunos de los resultados dependen de cálculos largos, cálculos paralelos, etc.) No tengo una buena idea sobre cómo resolver esto todavía. . Quizás un flujo de trabajo que involucre una extensión como ipycache podría funcionar, pero eso parece un poco demasiado complicado.
Notas
Eliminando (despojando) la salida
- Cuando el notebook está funcionando, se puede usar la opción de menú
Cell/All Output/Clear
para eliminar la salida. - Existen algunos scripts para eliminar resultados, como el script nbstripout.py que elimina el resultado, pero no produce el mismo resultado que el uso de la interfaz del notebook. Eventualmente, esto se incluyó en el ipython/nbconvert , pero se cerró indicando que los cambios ahora están incluidos en ipython/ipython , pero la funcionalidad correspondiente parece que aún no se ha incluido. (actualización) Dicho esto, la solución de Gregory Crosswhite muestra que esto es bastante fácil de hacer, incluso sin invocar ipython/nbconvert , por lo que este enfoque probablemente sea viable si se puede conectar correctamente. (Sin embargo, adjuntarlo a cada sistema de control de versiones , no parece ser una buena idea, esto de alguna manera debería conectarse al mecanismo del portátil.)
Grupos de noticias
Cuestiones
- 977: Peticiones de características de notebook (Abrir) .
- 1280: Borrar todo en la opción de guardar (Abrir) . (Sigue de esta discusión .)
- 3295: cuadernos autoexportados: solo exportar celdas marcadas explícitamente (Cerrado) . Resuelto por la extensión 11 Add writeandexecute magic (Merged) .
Solicitudes de extracción
- 1621: borrar en [] los números de solicitud en "Borrar todos los resultados" (Fusionado) . (Ver también 2519 (fusionado) .)
- 1563: mejoras de clear_output (fusionadas) .
- 3065: diff-habilidad de los cuadernos (Cerrado) .
- 3291: Agregue la opción para omitir las celdas de salida al guardar. (Cerrado) . Esto parece extremadamente relevante, sin embargo, se cerró con la sugerencia de usar un filtro "limpiar / manchar". Una pregunta relevante: ¿ qué puede usar si desea eliminar la salida antes de ejecutar git diff? Parece que no ha sido contestada.
- 3312: WIP: Enganches para guardar portátiles (Cerrado) .
- 3747: ipynb -> transformador ipynb (Cerrado) . Esto es rebasado en 4175 .
- 4175 .
- 142: Utilice STDIN en nbstripout si no se proporciona ninguna entrada (Abrir) .
¿Qué tal la idea discutida en la publicación a continuación, donde debe mantenerse la salida del cuaderno, con el argumento de que puede llevarle mucho tiempo generarla, y es útil ya que GitHub ahora puede renderizar cuadernos? Se agregaron ganchos de guardado automático para exportar archivos .py, que se usan para diffs y .html para compartir con miembros del equipo que no usan notebooks o git.
https://towardsdatascience.com/version-control-for-jupyter-notebook-3e6cef13392d
(2017-02)
estrategias
- on_commit ():
- elimina la salida> nombre.ipynb (
nbstripout
,) - elimina la salida> name.clean.ipynb (
nbstripout
,) - siempre
nbconvert
a python: name.ipynb.py (nbconvert
) - siempre convierta a markdown: name.ipynb.md (
nbconvert
,ipymd
)
- elimina la salida> nombre.ipynb (
- vcs.configure ():
- git difftool, mergetool: nbdiff y nbmerge desde nbdime
herramientas
-
nbstripout
:nbstripout
las salidas de un portátil- src: nbstripout.py
- src: other
-
pip install nbstripout; nbstripout install
-
-
ipynb_output_filter
:ipynb_output_filter
las salidas de un cuaderno -
ipymd
: convertir entre {Jupyter, Markdown, O''Reilly Atlas Markdown, OpenDocument, .py} -
nbdime
: "Herramientas para diferenciar y combinar los portátiles Jupyter". (2015)- src: https://github.com/jupyter/nbdime
- docs: http://nbdime.readthedocs.io/
-
nbdiff
: compara los cuadernos de una manera amigable con los terminales- nbdime nbdiff funciona como una herramienta de git diff : https://nbdime.readthedocs.io/en/latest/#git-integration-quickstart
-
nbmerge
: combinación de tres vías de portátiles con resolución automática de conflictos- nbdime nbmerge funciona como una herramienta de combinación de git
-
nbdiff-web
: muestra una gran variedad de cuadernos renderizados -
nbmerge-web
: le ofrece una herramienta de combinación de tres vías basada en web para portátiles -
nbshow
: presente un solo cuaderno de forma amigable para el terminal
-
Aquí está mi solución con git. Le permite simplemente agregar y confirmar (y dif) como de costumbre: esas operaciones no alterarán su árbol de trabajo, y al mismo tiempo (re) ejecutar un cuaderno no alterará su historial de git.
Aunque es probable que esto pueda adaptarse a otros VCS, sé que no satisface sus requisitos (al menos la agnosticidad de VSC). Aún así, es perfecto para mí, y aunque no es nada particularmente brillante, y muchas personas probablemente ya lo usan, no encontré instrucciones claras sobre cómo implementarlo al buscar en Google. Por lo tanto, puede ser útil para otras personas.
- Guarde un archivo con este contenido en algún lugar (para lo siguiente, asumamos
~/bin/ipynb_output_filter.py
) - Hazlo ejecutable (
chmod +x ~/bin/ipynb_output_filter.py
) Cree el archivo
~/.gitattributes
, con el siguiente contenido*.ipynb filter=dropoutput_ipynb
Ejecuta los siguientes 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
¡Hecho!
Limitaciones:
- funciona solo con git
- en git, si estás en una sucursal y haces
git checkout otherbranch; git checkout somebranch
git checkout otherbranch; git checkout somebranch
, normalmente se espera que el árbol de trabajo no segit checkout otherbranch; git checkout somebranch
. Aquí, en cambio, habrá perdido la salida y la numeración de las celdas de los cuadernos cuya fuente difiere entre las dos ramas. - más en general, la salida no está versionada en absoluto, como en la solución de Gregory. Para no solo deshacerse de él cada vez que haga algo relacionado con un proceso de pago, el enfoque podría cambiarse almacenándolo en archivos separados (pero tenga en cuenta que en el momento en que se ejecuta el código anterior, no se conoce el ID de confirmación). y posiblemente la versión de ellos (pero note que esto requeriría algo más que un
git commit notebook_file.ipynb
, aunque al menos mantendría agit diff notebook_file.ipynb
libre de basura base64). - Dicho esto, incidentalmente, si extrae un código (es decir, lo confirma alguien que no utiliza este método) que contiene alguna salida, la salida se verifica normalmente. Sólo se pierde la salida producida localmente.
Mi solución refleja el hecho de que, personalmente, no me gusta mantener la versión generada de las cosas generadas. Tenga en cuenta que es casi seguro que la fusión de la salida invalide la salida o su productividad, o ambas cosas.
EDITAR:
Si adopta la solución como lo sugerí, es decir, a nivel mundial, tendrá problemas en el caso de que git repo desee la versión de salida. Entonces, si desea deshabilitar el filtrado de salida para un repositorio git específico, simplemente cree dentro de él un archivo .git / info / attributes , con
**. filtro ipynb =
como contenido Claramente, de la misma manera es posible hacer lo contrario: habilitar el filtrado solo para un repositorio específico.
El código ahora se mantiene en su propio repositorio git.
Si las instrucciones anteriores dan como resultado ImportErrors, intente agregar "ipython" antes de la ruta del script:
git config --global filter.dropoutput_ipynb.clean ipython ~/bin/ipynb_output_filter.py
EDICIÓN : mayo de 2016 (actualizado en febrero de 2017): hay varias alternativas a mi script. Para completar, aquí hay una lista de las que conozco: nbstripout ( other variants ), nbstrip , jq .
Aquí hay una nueva solución de Cyrille Rossant para IPython 3.0, que persiste en reducir archivos en lugar de archivos ipymd basados en json:
Como se señala por, el --script
está en desuso en 3.x
Este enfoque se puede utilizar mediante la aplicación de un enlace posterior a guardar. En particular, agregue lo siguiente 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
El código se toma de # 8009 .
Desafortunadamente, no sé mucho sobre Mercurial, pero puedo darte una posible solución que funciona con Git, con la esperanza de que puedas traducir mis comandos de Git a sus equivalentes de Mercurial.
Para el fondo, en Git el comando add
almacena los cambios que se han realizado en un archivo en un área de ensayo. Una vez que haya hecho esto, Git ignorará cualquier cambio posterior en el archivo, a menos que usted le indique que también los ponga en escena. Por lo tanto, la siguiente secuencia de comandos, que, para cada uno de los archivos dados, elimina todas las outputs
y prompt_number sections
, escalona el archivo eliminado y luego restaura el original:
NOTA: Si ejecuta esto ImportError: No module named IPython.nbformat
un mensaje de error como ImportError: No module named IPython.nbformat
, luego use ipython
para ejecutar el script en lugar 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)
Una vez que el script se haya ejecutado en los archivos cuyos cambios desea comprometer, simplemente ejecute git commit
.
Después de excavar, finalmente encontré este gancho de guardado previo relativamente simple en los documentos de Jupyter . Elimina los datos de salida de la celda. jupyter_notebook_config.py
pegarlo en el archivo jupyter_notebook_config.py
(consulte las instrucciones a continuación).
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
De la respuesta de Rich Signell :
Si no está seguro en qué directorio encontrar su archivo
jupyter_notebook_config.py
, puede escribirjupyter --config-dir
[en el símbolo del sistema / terminal], y si no encuentra el archivo allí, puede crearlo escribiendojupyter notebook --generate-config
.
Esta extensión jupyter permite a los usuarios empujar los portátiles jupyter directamente a github.
Por favor mira aqui
He construido un paquete de Python que resuelve este problema
https://github.com/brookisme/gitnb
Proporciona un CLI con una sintaxis inspirada en git para rastrear / actualizar / diferenciar cuadernos dentro de su repositorio de git.
Heres ''un ejemplo
# 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"
Tenga en cuenta que el último paso, donde estoy usando "gitnb commit" es comprometerse con su repositorio git. Es esencialmente una envoltura 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"
Hay varios métodos más, y se pueden configurar de modo que requiera más o menos aportación del usuario en cada etapa, pero esa es la idea general.
He creado other , basado en la nbstripout.py , que soporta Git y Mercurial (gracias a mforbes). Está diseñado para ser utilizado de forma independiente en la línea de comandos o como un filtro, que se instala fácilmente en el repositorio actual mediante la nbstripout install
/ nbstripout uninstall
.
pip install nbstripout
Hice lo que hicieron Albert y Rich: no versión los archivos .ipynb (ya que estos pueden contener imágenes, lo que puede causar problemas). En su lugar, ejecute siempre ipython notebook --script
o ponga c.FileNotebookManager.save_script = True
en su archivo de configuración, de modo que siempre se cree un archivo .py
(de ipython notebook --script
) cuando guarde su notebook.
Para regenerar cuadernos (después de revisar un repositorio o cambiar una rama), coloco el script py_file_to_notebooks.py en el directorio donde py_file_to_notebooks.py mis cuadernos.
Ahora, después de revisar un repositorio, simplemente ejecute python py_file_to_notebooks.py
para generar los archivos ipynb. Después de cambiar de rama, es posible que deba ejecutar python py_file_to_notebooks.py -ov
para sobrescribir los archivos ipynb existentes.
Solo para estar seguro, también es bueno agregar *.ipynb
a su archivo .gitignore
.
Edición: ya no hago esto porque (A) tienes que regenerar tus cuadernos a partir de archivos py cada vez que compruebas una sucursal y (B) hay otras cosas como la reducción en los cuadernos que pierdes. En lugar de eso, elimino la salida de los portátiles con un filtro git. La discusión sobre cómo hacer esto está nbstripout.py .
Ok, entonces parece que la mejor solución actual, como se explica nbstripout.py , es hacer un filtro git para eliminar automáticamente la salida de los archivos ipynb en la confirmación.
Esto es lo que hice para que funcione (copiado de esa discusión):
Modifiqué ligeramente el archivo nbstripout de cfriedline para dar un error informativo cuando no se puede importar el último IPython: https://github.com/petered/plato/blob/fb2f4e252f50c79768920d0e47b870a8d799e92b/notebooks/config/strip_notebook_output decir en ./relative/path/to/strip_notebook_output
También se agregó el archivo .gitattributes a la raíz del repositorio, que contiene:
*.ipynb filter=stripoutput
Y creó un setup_git_filters.sh
contiene
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
Y corrió la source setup_git_filters.sh
. La cosa elegante de $ (git rev-parse ...) es encontrar la ruta local de tu repositorio en cualquier máquina (Unix).
Para continuar con el excelente guión de Pietro Battiston, si aparece un error de análisis de 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)
Puedes agregar al principio del script:
reload(sys)
sys.setdefaultencoding(''utf8'')
Tenemos un proyecto de colaboración en el que el producto es Jupyter Notebooks, y hemos utilizado un enfoque durante los últimos seis meses que está funcionando muy bien: activamos guardar los archivos .py
automáticamente y .ipynb
tanto los archivos .ipynb
como los archivos .py
.
De esa manera, si alguien quiere ver / descargar la última notebook, puede hacerlo a través de github o nbviewer, y si alguien quiere ver cómo ha cambiado el código de la notebook, solo pueden ver los cambios en los archivos .py
.
Para los servidores de portátiles Jupyter
, esto se puede lograr agregando las líneas
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
al archivo jupyter_notebook_config.py
y reinicie el servidor de notebook.
Si no está seguro en qué directorio encontrar su archivo jupyter_notebook_config.py
, puede escribir jupyter --config-dir
, y si no encuentra el archivo allí, puede crearlo escribiendo el jupyter notebook --generate-config
.
Para los Ipython 3
notebook Ipython 3
, esto se puede lograr agregando las líneas
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
al archivo ipython_notebook_config.py
y reinicie el servidor de notebook. Estas líneas son de una respuesta de problemas de github que se proporciona @minrk y @dror también las incluye en su respuesta SO.
Para los Ipython 2
notebook Ipython 2
, esto se puede lograr iniciando el servidor usando:
ipython notebook --script
o añadiendo la línea
c.FileNotebookManager.save_script = True
al archivo ipython_notebook_config.py
y reinicie el servidor de notebook.
Si no está seguro en qué directorio encontrar su archivo ipython_notebook_config.py
, puede escribir ipython locate profile default
, y si no encuentra el archivo allí, puede crearlo escribiendo ipython profile create
.
Aquí está nuestro proyecto en github que utiliza este enfoque : y aquí hay un ejemplo de github de explorar los cambios recientes en un cuaderno .
Hemos estado muy contentos con esto.
Yo uso un enfoque muy pragmático; Que funcionan bien para varios cuadernos, en varios lados. Y hasta me permite ''transferir'' cuadernos alrededor. Funciona tanto para Windows como para Unix / MacOS.
Al pensarlo es simple, es resolver los problemas anteriores ...
Concepto
Básicamente, no .ipnyb
archivos .ipnyb
, solo los archivos .py
correspondientes.
Al iniciar el servidor portátil con la opción --script
, ese archivo se crea / guarda automáticamente cuando se guarda el cuaderno.
Esos archivos .py
contienen toda la entrada; El no código se guarda en comentarios, al igual que los bordes de las celdas. Esos archivos pueden ser leídos / importados (y arrastrados) al servidor de notebook para (re) crear un notebook. Sólo la salida se ha ido; hasta que se vuelva a ejecutar.
Personalmente uso mercurial para realizar un seguimiento de versión de los archivos .py
; y use los comandos normales (línea de comando) para agregar, registrar (ect) para eso. La mayoría de los otros (D) VCS lo permitirán.
Es simple seguir la historia ahora; Los .py
son pequeños, textuales y sencillos de diferenciar. De vez en cuando, necesitamos un clon (solo ramificación; comenzar allí un segundo servidor de computadoras portátiles), o una versión anterior (compruebe e importe en un servidor de computadoras portátiles), etc.
Consejos y trucos
- Agregue * .ipynb a '' .hgignore '', para que Mercurial sepa que puede ignorar esos archivos.
- Cree un script (bash) para iniciar el servidor (con la opción
--script
) y haga un seguimiento de la versión - Guardar un cuaderno guarda el archivo
.py
, pero no lo registra.- Esto es un inconveniente : se puede olvidar que
- También es una característica : es posible guardar un cuaderno (y continuar más adelante) sin agrupar el historial de repositorios.
Deseos
- Sería bueno tener botones para registrar / agregar / etc en el tablero de instrumentos de la computadora portátil
- Un checkout a (por ejemplo)
file@date+rev.py
) debería ser útil Sería mucho trabajo agregar eso; y tal vez lo haré una vez. Hasta ahora, solo lo hago a mano.