Curso
Em ciência de dados, muitas vezes você vai precisar criar relatórios do seu trabalho para apresentar a tomadores de decisão ou a pessoas não técnicas. Converter seu Jupyter Notebook em um documento PDF ou HTML estável facilita o compartilhamento com colegas que não têm Python ou o Jupyter instalado. O Python usa a biblioteca nbconvert e a linguagem de template Jinja2 para converter documentos. Os templates definem como um documento será exibido em uma página web ou em outro formato de saída. Entender como personalizar templates ajuda muito a criar relatórios bonitos dos seus Notebooks.
Jupyter Notebooks são uma forma de executar código Python no navegador. Se você quer aprender mais sobre Python, confira nosso curso gratuito Intro to Python for Data Science.
Neste tutorial, você vai ver:
- Templates e o designer de templates
Jinja2. - Renderização de templates e herança.
- Como usar o
nbconvertpara exportar seus Notebooks. - A sintaxe e a estrutura para estender os templates padrão do Jupyter para exportação em HTML.
- As diferenças ao exportar para LaTeX e PDF.
O que são templates?
Da wiki do Python: "Templating, e em particular web templating, é uma forma de representar dados em diferentes formatos... Frequentemente, soluções de templating envolvem um documento (o template) e dados. Os templates geralmente se parecem muito com a saída final, com espaços reservados no lugar dos dados reais"
O Jupyter Notebook pode ser exportado facilmente usando File -> Download As (no Jupyter Lab, você verá Export Notebook As). Essa opção usa os templates padrão do ambiente principal do Jupyter. O Jinja2 é uma linguagem poderosa de templates em Python para definir blocos e formatação. Os templates têm seções, definidas por tags, que instruem o template sobre como renderizar os dados de entrada. Os dados substituem as variáveis ou expressões quando o template é renderizado.
Os delimitadores para as diferentes seções de um template são:
{% ... %}para statements (comandos){{ ... }}para expressions (expressões) que serão impressas na saída{# ... #}para comentários que não entram na saída
Dê uma olhada neste exemplo curto de um template Jinja (cortesia da documentação do 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>
Você pode ver acima que boa parte do template se parece com um documento HTML normal. O Jinja precisa de algumas linhas extras para interpretar o código como um template. O {% block head %} define a seção head do documento HTML e como este template vai estender sua formatação. A seção {% block title %} descreve onde o título de entrada será exibido. E o {% endblock %} aparece em vários lugares porque ele encerra o bloco de template correspondente.
Agora você está pronto para aprender a usar o Jinja e criar seus próprios templates personalizados!
Introdução ao Jinja
Jinja é um mecanismo de template que permite definir como seus documentos serão exibidos. Especificamente, neste tutorial, o foco é como exportar seu Jupyter Notebook com a ajuda de templates Jinja.
Primeiro, importe de Jinja2 o Template:
from jinja2 import Template
Entendendo a renderização básica com templates
Renderizar dados em templates Jinja é bem direto. Usando chaves e nomes de variáveis, você pode exibir seus dados no template:
myTemplate = Template("My template is {{ something }}!")
myTemplate.render(something="awesome")
'My template is awesome!'
Chamar .render(input) permite exibir o template no seu Notebook com sua entrada substituindo o {{ something }} genérico do template. Este é um exemplo de uso de expression no Jinja.
Você pode usar um template de forma mais dinâmica definindo um statement em Python, que o Jinja vai 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 "
Perceba que você definiu um loop for e o template renderizou os números de 0 a 10 com espaços entre eles. A sintaxe de statement com {% expression %} oferece bastante flexibilidade para criar um template personalizado.
Herança de templates
Para criar templates que estendem as exportações padrão do Jupyter, você pode usar herança de template. Herança é o conceito na programação em que você implementa algo em um objeto filho que aproveita funcionalidades definidas no pai. Um exemplo simples: se você tem um objeto Dog que pode bark, eat e walk, pode usar herança para criar um objeto Dalmatian que herda esses atributos e adiciona outros, como has spots. Você verá neste tutorial como a herança é útil para templating, porque normalmente só são necessárias pequenas mudanças para personalizar a saída. Assim como em outras linguagens com herança, o Jinja usa palavras-chave como extends e super para acessar definições do pai.
Veja o template pai do exemplo acima, chamado 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>
Um template filho poderia ser assim (cortesia da documentação do 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 %}
Note como o template filho começa com {% extends "base.html" %}. Essa declaração informa ao mecanismo de templates Jinja como tratar este documento (como HTML com herança de base.html). Usar esse template filho permite especificar atributos diferentes para a homepage (como uma cor de CSS específica). Além disso, você pode ver no head block que o template filho herda o estilo do pai com a chamada a super().
Usando o nbconvert para exportar Jupyter Notebooks
Agora que você entendeu a sintaxe básica e a herança de templates, vamos ver como exportar seu Jupyter Notebook com o nbconvert e definir templates para personalizar a saída.
Primeiro, importe o nbconvert:
import nbconvert
O nbconvert pode gerar saídas em vários formatos. Este tutorial vai focar em HTML e LaTeX/PDF. Para visualizar a saída no notebook, use a função de display do IPython e o --stdout na sua chamada ao 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

Esse trecho de um Notebook fica muito parecido com o arquivo .ipynb original. A saída é exibida como um documento independente, com títulos e texto como normalmente aparece no Notebook. Os blocos de células de código são exibidos como caixas cinzas (o chamado "Notebook style"). A principal diferença visível é que o template HTML padrão do Jupyter não exibe os prompts "Out:", como fazem os Notebooks ativos.
Usando nbconvert e um template filho personalizado para exportar Jupyter Notebooks
Normalmente, você vai querer estender os templates de exportação padrão do Jupyter Notebook e fazer pequenas alterações de design na sua saída.
Por exemplo, veja este template simples que remove as células de Markdown da saída (chamado rmMkdwn.tpl):
{% extends 'basic.tpl'%}
{% block markdowncell -%}
{% endblock markdowncell %}
Aplicar um template no nbconvert é feito com a opção --template=:
example = !jupyter nbconvert --to html 'Example.ipynb' --template='rmMkdwn.tpl' --stdout
display(HTML('\n'.join(example)))
[NbConvertApp] Converting notebook Example.ipynb to html
Mesmo um template muito simples, como o rmMkdwn.tpl, já ajuda bastante a customizar sua saída.
Veja este template um pouco mais elaborado, que coloca uma borda vermelha nas células (chamado 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 
Acima, você vê que a única mudança de estilo foram as bordas vermelhas ao redor de cada célula. O super() é usado para garantir que cada célula mantenha sua formatação individual do template pai full.tpl.
Diferenças nos templates para exportação em LaTeX e PDF
Como { } e % são caracteres especiais no LaTeX, você precisa usar (( )) e *. Além disso, os templates LaTeX padrão são base.tplx, article.tplx e report.tplx, que correspondem às classes de documento do LaTeX.
Veja como remover células de Markdown com um template filho em LaTeX:
((* extends 'article.tplx' *))
((* block markdowncell -*))
((* endblock markdowncell *))
O nbconvert pode gerar saída LaTeX ou PDF — o PDF é compilado a partir dos templates LaTeX. Para facilitar a visualização neste tutorial, veja o que acontece ao exportar para PDF usando o 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

Perceba que esta saída ficou bem diferente da versão em HTML. O LaTeX usa a primeira célula de Markdown como título do artigo, então ela ainda aparece na saída. O template removeu corretamente o subtítulo (ou seja, tratou aquela célula como Markdown de fato). A classe de documento article insere a data de hoje — algo que não estava no Notebook original. A fonte e o espaçamento também diferem da versão HTML. Por fim, por padrão a saída usa o estilo clássico do IPython, não o display em "Notebook style".
Se você optar por estender uma classe de documento LaTeX que já vem no Jupyter, certifique-se de entender qual formatação ela aplica.
Conclusão
Alguns pontos-chave sobre templates, Jinja2 e nbconvert:
Templates definem como um documento será exibido em uma página web ou em outro formato de saída. O Jupyter Notebook implementa templates Jinja para exibir diferentes formatos de exportação. Templates têm regras de herança que permitem definir templates pai e filho para páginas semelhantes, com pequenas diferenças de formatação.
Templates Jinja possuem statements, expressions e (opcionalmente) comments. Statements, definidos por {% statement %}, definem a estrutura do template. Expressions, definidos por {{ expression }}, preenchem o template com seus dados. E comments, definidos por {# comment #}, não aparecem na saída — são internos ao template.
O nbconvert é uma biblioteca Python que permite converter seu Jupyter Notebook em outros formatos, como HTML, LaTeX e PDF. O nbconvert usa templates Jinja para definir como os Notebooks serão exibidos nesses formatos. Você pode definir templates personalizados ou estender os templates padrão do Jupyter usando herança.
Agora você está pronto para começar a criar seus próprios templates e exportar Jupyter Notebooks lindos!
Referências:

