Curso
En ciencia de datos, a menudo tendrás que crear informes de tu trabajo para mostrarlos a responsables de negocio u otros perfiles no técnicos. Convertir tu Jupyter Notebook en un documento PDF o HTML estable es más cómodo de compartir con colegas que no tienen Python ni Jupyter instalados. Python utiliza una librería llamada nbconvert y un lenguaje de plantillas llamado Jinja2 para convertir documentos. Las plantillas definen cómo se mostrará un documento en una página web u otro formato de salida. Entender cómo personalizarlas te ayudará a crear informes con una mejor presentación de tus Notebooks.
Los Jupyter notebooks son una forma de ejecutar código Python en el navegador. Si quieres aprender más sobre Python, echa un vistazo a nuestro curso gratuito Intro to Python for Data Science.
En este tutorial, verás los siguientes temas:
- Plantillas y el diseñador de plantillas
Jinja2. - Renderizado de plantillas y herencia.
- Cómo usar
nbconvertpara exportar tus Notebooks. - Sintaxis y estructura para ampliar las plantillas predeterminadas de Jupyter para exportación a HTML.
- Diferencias al exportar a formatos LaTeX y PDF.
¿Qué son las plantillas?
De la wiki de Python: "El templating, y en particular el templating web, es una forma de representar los datos en diferentes formas... A menudo, las soluciones de templating implican un documento (la plantilla) y datos. Las plantillas suelen parecerse mucho al resultado final, con marcadores de posición en lugar de datos reales"
El Jupyter Notebook puede exportarse fácilmente usando File -> Download As (en Jupyter Lab verás Export Notebook As). Esta opción utiliza las plantillas por defecto del entorno principal de Jupyter. Jinja2 es un potente lenguaje de plantillas para Python que permite definir bloques y composición. Las plantillas tienen secciones, definidas por etiquetas, que indican cómo debe renderizarse la entrada. Los datos sustituyen a las variables o expresiones cuando se renderiza la plantilla.
Los delimitadores para las diferentes secciones de una plantilla son:
{% ... %}para sentencias{{ ... }}para expresiones que se imprimen en la salida{# ... #}para comentarios que no se incluyen en la salida
Mira este breve ejemplo de una plantilla Jinja (cortesía de la documentación de Jinja2):
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage </title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid">you </a>.
{% endblock %}
</div>
</body>
</html>
Como ves, gran parte de la plantilla se parece a un documento HTML normal. Jinja necesita unas pocas líneas extra para interpretar el código como una plantilla. El {% block head %} define la sección head del documento HTML y cómo esta plantilla ampliará su formato. La sección {% block title %} indica dónde se mostrará el título de entrada. Y {% endblock %} aparece en varios lugares porque cierra cada bloque correspondiente.
¡Ahora ya puedes aprender a usar Jinja para crear tus propias plantillas personalizadas!
Introducción a Jinja
Jinja es un motor de plantillas que te permite definir cómo se mostrarán tus documentos. En concreto, en este tutorial te centrarás en cómo exportar tu Jupyter Notebook con la ayuda de plantillas Jinja.
Primero, importa Template desde Jinja2:
from jinja2 import Template
Entender el renderizado básico con plantillas
Renderizar datos en plantillas Jinja es bastante directo. Usando llaves y nombres de variables, puedes mostrar tus datos en la plantilla:
myTemplate = Template("My template is {{ something }}!")
myTemplate.render(something="awesome")
'My template is awesome!'
Llamar a .render(input) te permite mostrar la plantilla en tu Notebook con tu entrada sustituyendo al genérico {{ something }} de la plantilla. Este es un ejemplo de uso de expresiones en Jinja.
Puedes usar una plantilla de forma más dinámica definiendo una sentencia de Python, que Jinja interpretará.
myFancyTemplate = Template("Let's count to 10: {% for i in range(11) %}{{i}} " "{% endfor %}")
myFancyTemplate.render()
"Let's count to 10: 0 1 2 3 4 5 6 7 8 9 10 "
Fíjate que has definido un bucle for y la plantilla ha renderizado los números del 0 al 10 con espacios entre ellos. La sintaxis de sentencias {% expression %} aporta mucha flexibilidad para crear una plantilla a medida.
Herencia de plantillas
Para crear plantillas que amplíen las exportaciones predeterminadas de Jupyter, puedes usar herencia de plantillas. La herencia es el concepto de programación según el cual puedes implementar algo en un objeto hijo que aprovecha la funcionalidad definida por el padre. Un ejemplo sencillo: si tienes un objeto Dog que puede bark, eat y walk, puedes usar herencia para crear un objeto Dalmatian que hereda esos atributos y añade otros, como has spots. Verás en este tutorial lo útil que es la herencia para las plantillas, porque normalmente solo necesitas pequeños cambios para personalizar la salida. Como otros lenguajes con herencia, Jinja usa palabras clave como extends y super para acceder a definiciones del padre.
Observa el ejemplo de plantilla padre de arriba, llamada base.html:
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage </title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid">you </a>.
{% endblock %}
</div>
</body>
</html>
Una plantilla hija podría ser así (cortesía de la documentación de Jinja2):
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
{% endblock %}
Fíjate en cómo la plantilla hija empieza con {% extends "base.html" %}. Esta declaración le dice al motor de plantillas Jinja cómo tratar este documento (como un HTML con herencia de base.html). Al usar esta plantilla hija puedes especificar distintos atributos para la página de inicio (como un color CSS concreto). Además, en el bloque head la hija hereda el estilo del padre con la llamada a super().
Usar nbconvert para exportar Jupyter Notebooks
Ahora que entiendes la sintaxis básica y la herencia de plantillas, puedes aprender a exportar tu Jupyter Notebook con nbconvert y definir plantillas para personalizar la salida.
Primero, importa nbconvert:
import nbconvert
nbconvert puede generar salida en varios formatos. Este tutorial se centra en HTML y LaTeX/PDF. Para ver la salida en el notebook, usa la función de visualización de IPython y la opción --stdout en tu llamada a nbconvert.
example = !jupyter nbconvert --to html 'Example.ipynb' --stdout
from IPython.display import HTML, display
display(HTML('\n'.join(example)))
[NbConvertApp] Converting notebook Example.ipynb to html

Este fragmento de un Notebook se parece mucho al archivo .ipynb original. La salida se muestra como un documento independiente, con encabezados y texto como suele verse en un Notebook. Los bloques de celdas de código aparecen como cajas grises (lo que se conoce como "estilo Notebook"). La principal diferencia visible es que la plantilla HTML predeterminada de Jupyter no muestra los prompts "Out:", como sí hacen los Notebooks activos.
Usar nbconvert y una plantilla hija personalizada para exportar Jupyter Notebooks
Normalmente, querrás ampliar las plantillas de exportación predeterminadas de Jupyter Notebook y hacer pequeños cambios de diseño en tu salida.
Por ejemplo, mira esta plantilla sencilla que elimina las celdas de Markdown de la salida (llamada rmMkdwn.tpl):
{% extends 'basic.tpl'%}
{% block markdowncell -%}
{% endblock markdowncell %}
Aplicar una plantilla a nbconvert se hace con la opción --template=:
example = !jupyter nbconvert --to html 'Example.ipynb' --template='rmMkdwn.tpl' --stdout
display(HTML('\n'.join(example)))
[NbConvertApp] Converting notebook Example.ipynb to html
Incluso una plantilla muy simple, como rmMkdwn.tpl, puede ayudarte muchísimo a personalizar la salida.
Mira esta plantilla algo más compleja, que enmarca las celdas en rojo (llamada boxRed.tpl):
{% extends 'full.tpl'%}
{% block any_cell %}
<div style="border:thin solid red">
{{ super() }}
</div>
{% endblock any_cell %}
example = !jupyter nbconvert --to html 'Example.ipynb' --template='boxRed.tpl' --stdout
display(HTML('\n'.join(example)))
[NbConvertApp] Converting notebook Example.ipynb to html 
Arriba puedes ver que el único cambio de estilo son los bordes rojos alrededor de cada celda. Se usa super() para asegurar que cada celda mantiene su estilo individual heredado de la plantilla padre full.tpl.
Diferencias en las plantillas para exportar a LaTeX y PDF
Como { } y % son caracteres especiales en LaTeX, tienes que usar (( )) y *. Además, las plantillas LaTeX predeterminadas son base.tplx, article.tplx y report.tplx, que corresponden a clases de documento de LaTeX.
Veamos cómo eliminar celdas de Markdown con una plantilla hija de LaTeX:
((* extends 'article.tplx' *))
((* block markdowncell -*))
((* endblock markdowncell *))
nbconvert puede generar salida en LaTeX o PDF: el PDF se compila a partir de las plantillas LaTeX. Para facilitar la visualización en este tutorial, observa qué ocurre al exportar a PDF usando rmMkdwn.tplx.
!jupyter nbconvert --to pdf 'Example.ipynb' --template='rmMkdwn.tplx'
from IPython.display import IFrame
IFrame('Example.pdf', width=800, height=500)
[NbConvertApp] Converting notebook Example.ipynb to pdf
[NbConvertApp] Support files will be in Example_files/
[NbConvertApp] Making directory Example_files
[NbConvertApp] Writing 16047 bytes to notebook.tex
[NbConvertApp] Building PDF
[NbConvertApp] Running xelatex 3 times: ['xelatex', 'notebook.tex']
[NbConvertApp] Running bibtex 1 time: ['bibtex', 'notebook']
[NbConvertApp] WARNING | bibtex had problems, most likely because there were no citations
[NbConvertApp] PDF successfully created
[NbConvertApp] Writing 16102 bytes to Example.pdf

Fíjate: esta salida es bastante distinta de la versión HTML. LaTeX usa la primera celda de Markdown como título del artículo, así que sigue mostrándose. La plantilla sí eliminó el subtítulo (es decir, consideró esa celda como Markdown real). La clase de documento del artículo añade la fecha de hoy, algo que no está en el Notebook original. La tipografía y el espaciado también difieren de la versión HTML. Por último, de forma predeterminada la salida usa el estilo clásico de IPython, no el "estilo Notebook".
Si decides ampliar una clase de documento LaTeX incluida en Jupyter, asegúrate de entender qué formato aplicará.
Conclusión
Hay algunos puntos clave que recordar sobre las plantillas, Jinja2 y nbconvert.
Las plantillas definen cómo se mostrará un documento en una página web u otro formato de salida. Jupyter Notebooks implementa plantillas Jinja para mostrar diferentes formatos de exportación. Las plantillas tienen reglas de herencia que te permiten definir plantillas padre e hija para páginas similares con un formato ligeramente distinto.
Las plantillas Jinja tienen sentencias, expresiones y (opcionalmente) comentarios. Las sentencias, definidas con {% statement %}, marcan la estructura de la plantilla. Las expresiones, definidas con {{ expression }}, rellenan la plantilla con tus datos. Y los comentarios, definidos con {# comment #}, no se muestran en la salida: son solo internos de la plantilla.
nbconvert es una librería de Python que te permite convertir tu Jupyter Notebook a otros formatos, como HTML, LaTeX y PDF. nbconvert utiliza plantillas Jinja para definir cómo se mostrarán los Jupyter Notebooks en esos formatos. Puedes definir plantillas personalizadas o ampliar las plantillas predeterminadas de Jupyter usando herencia.
¡Ahora estás listo para crear tus propias plantillas y exportar Jupyter Notebooks con una presentación impecable!
Referencias:


