课程
在数据科学中,您经常需要将自己的工作整理成报告,展示给决策者或其他非技术人员。将您的 Jupyter 笔记本 转换为稳定的 PDF 或 HTML 文档,更便于没有安装 Python 或 Jupyter 的同事查看。Python 使用名为 nbconvert 的库和名为 Jinja2 的模板语言来转换文档。模板定义了文档在网页或其他输出格式中的呈现方式。了解如何自定义模板,有助于为您的笔记本制作更美观的报告。
Jupyter 笔记本是一种在浏览器中运行 Python 代码的方式。如果您想进一步了解 Python,欢迎查看我们的免费课程 面向数据科学的 Python 入门。
在本教程中,您将学习以下内容:
- 模板与
Jinja2模板设计器。 - 模板渲染与继承。
- 如何使用
nbconvert导出您的笔记本。 - 扩展 Jupyter 默认 HTML 导出模板的语法与结构。
- 导出到 LaTeX 与 PDF 格式的差异。
什么是模板?
引用 Python 维基:“模板化,特别是网页模板化,是以不同形式呈现数据的一种方式……通常,模板解决方案包含一个文档(模板)和数据。模板通常与最终输出非常相似,只是把实际数据换成了占位符。”
Jupyter 笔记本可以通过 File -> Download As(在 Jupyter Lab 中,您会看到 Export Notebook As)轻松导出。该选项使用存放在主 Jupyter 环境中的默认模板。Jinja2 是一个功能强大的 Python 模板语言,用于定义区块和排版。模板包含由标签定义的各个部分,指示模板如何渲染输入数据。渲染时,数据会替换模板中的变量或表达式。
模板不同部分的定界符为:
{% ... %}:语句{{ ... }}:要打印到模板输出的表达式{# ... #}:不会包含在模板输出中的注释
看一个简短的 Jinja 模板示例(摘自 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>
您可以看到,上面的模板与普通 HTML 文档非常相似。Jinja 只需要少量额外语句即可将代码按模板解释。{% block head %} 定义了 HTML 文档的 head 区域,以及该模板将如何扩展其格式。{% block title %} 指定了输入标题显示的位置。{% endblock %} 多次出现,因为它用于结束相应的模板区块。
现在,您已经可以开始学习如何使用 Jinja 来创建属于自己的自定义模板了!
Jinja 简介
Jinja 是一个模板引擎,允许您定义文档的显示方式。具体来说,在本教程中,您将重点学习如何借助 Jinja 模板导出您的 Jupyter 笔记本。
首先,从 Jinja2 导入 Template:
from jinja2 import Template
理解模板的基础渲染
在 Jinja 模板中渲染数据相当直接。使用花括号和变量名,您就可以在模板中显示数据:
myTemplate = Template("My template is {{ something }}!")
myTemplate.render(something="awesome")
'My template is awesome!'
调用 .render(input) 可以让您在笔记本中显示模板,并用您的输入替换模板中的通用占位 {{ something }}。这是 Jinja 中使用表达式的一个示例。
您还可以通过定义 Python 语句,让模板更具动态性,Jinja 会对其进行解释。
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 "
注意,您定义了一个 for 循环,模板按空格分隔渲染了 0 到 10 的数字。{% expression %} 这样的语句语法在制作自定义模板时提供了很大的灵活性。
模板继承
为了扩展 Jupyter 默认的导出模板,您可以使用模板继承。继承是编程中的一个概念,指您可以在子对象中实现某些功能,同时利用父对象已定义的功能。一个简单例子是,如果您有一个可以叫、吃、走的 Dog 对象,您可以使用继承创建一个 Dalmatian(斑点狗)对象,继承这些属性并新增如有斑点等属性。您将在本教程中看到继承在模板中的作用非常大,因为通常只需做一些小改动即可自定义输出。与其他具备继承特性的语言类似,Jinja 使用 extends 与 super 等关键字来访问父级定义。
看看上面名为 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>
一个子模板可能如下所示(摘自 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 %}
请注意,子模板以 {% extends "base.html" %} 开头。该声明告诉 Jinja 模板引擎如何处理此文档(作为继承自 base.html 的 HTML)。使用该子模板可以为首页指定不同属性(例如特定的 CSS 颜色)。此外,您可以在head 区块中看到,子模板通过调用 super() 继承了父模板的样式。
使用 nbconvert 导出 Jupyter 笔记本
现在您已经了解了模板的基本语法和继承机制,接下来学习如何使用 nbconvert 导出 Jupyter 笔记本,并通过定义模板自定义输出。
首先导入 nbconvert:
import nbconvert
nbconvert 可以生成多种输出格式。本教程将重点介绍 HTML 和 LaTeX/PDF 输出。要在笔记本中查看输出,请使用 IPython 的 display 函数,并在调用 nbconvert 时添加 --stdout。
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

这段笔记本的输出与原始 .ipynb 文件非常相似。输出以独立文档的形式显示,标题和文本的呈现方式与笔记本中通常的显示接近。代码单元格以灰色方框显示(称为“Notebook 风格”)。您能看到的主要差异是,Jupyter 默认的 HTML 模板不会显示类似于活动笔记本中的 “Out:” 提示。
使用 nbconvert 和自定义子模板导出 Jupyter 笔记本
通常,您会希望在 Jupyter 笔记本的默认导出模板之上进行扩展,并对输出做一些小的设计更改。
例如,看看这个从输出中移除 Markdown 单元格的简单模板(名为 rmMkdwn.tpl):
{% extends 'basic.tpl'%}
{% block markdowncell -%}
{% endblock markdowncell %}
将模板应用于 nbconvert 可通过 --template= 选项实现:
example = !jupyter nbconvert --to html 'Example.ipynb' --template='rmMkdwn.tpl' --stdout
display(HTML('\n'.join(example)))
[NbConvertApp] Converting notebook Example.ipynb to html
即便是像 rmMkdwn.tpl 这样非常简单的模板,也能极大地帮助您自定义输出。
再看一个更复杂的模板,它为单元格加上红色边框(名为 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 
如上所示,唯一的样式更改是每个单元格外面的红色边框。使用 super() 可确保每个单元格保留其从父模板 full.tpl 继承的个体样式。
导出到 LaTeX 与 PDF 时模板的差异
由于 { } 和 % 在 LaTeX 中是特殊字符,因此需要改用 (( )) 和 *。此外,默认的 LaTeX 模板为 base.tplx、article.tplx 和 report.tplx,它们分别对应 LaTeX 的文档类。
看看如何通过 LaTeX 子模板移除 Markdown 单元格:
((* extends 'article.tplx' *))
((* block markdowncell -*))
((* endblock markdowncell *))
nbconvert 可以生成 LaTeX 或 PDF 输出——PDF 输出由 LaTeX 模板编译而成。为便于本教程中的查看,看看使用 rmMkdwn.tplx 导出为 PDF 时会发生什么。
!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

请注意,该输出与 HTML 版本有很大不同。LaTeX 会将第一个 Markdown 单元格作为论文标题处理,因此它仍会显示在输出中。模板确实移除了副标题(因此它把那个 Markdown 单元格视为真正的 Markdown)。文章文档类会更新为今天的日期——这在原始笔记本中并不存在。字体和行距也不同于 HTML 版本。最后,默认情况下输出使用经典的 IPython 显示风格,而不是“Notebook 风格”。
如果您选择扩展 Jupyter 中的某个 LaTeX 文档类,请确保了解它将采用的格式设置。
总结
关于模板、Jinja2 和 nbconvert,有一些关键点需要记住。
模板定义了文档在网页或其他输出格式中的显示方式。Jupyter 笔记本使用 Jinja 模板来呈现不同的导出格式。模板具有继承规则,允许您为相似页面定义父模板和子模板,只做些许格式差异。
Jinja 模板包含 语句、表达式 和(可选的)注释。用 {% statement %} 定义的语句用于定义模板结构。用 {{ expression }} 定义的表达式用您的数据填充模板。而用 {# comment #} 定义的注释不会显示在输出中——它们仅用于模板内部。
nbconvert 是一个 Python 库,可将您的 Jupyter 笔记本转换为其他格式,例如 HTML、LaTeX 和 PDF。nbconvert 使用 Jinja 模板来定义笔记本在这些格式中的显示方式。您可以定义自定义模板,或使用继承扩展 Jupyter 的默认模板。
现在,您已经可以开始制作自己的模板,并导出精美的 Jupyter 笔记本文档了!
参考资料: