端到端演练

nbdev 使用分步指南

下面的书面教程将向您展示如何使用 nbdev 从零开始创建一个 Python 包。

或者,您可以观看此视频教程,Jeremy Howard 和 Hamel Husain 将逐步指导您完成类似的过程

安装

您需要以下软件来完成本教程,请继续阅读以获取具体的安装说明

  1. Python
  2. Python 包管理器:我们推荐 conda 或 pip
  3. Jupyter Notebook
  4. nbdev
  5. Quarto

如果您之前没有使用过 Python,我们建议您从 Anaconda Individual Edition 入手,并使用 conda 包管理器。

请注意,您只需为每个环境执行安装部分中的步骤一次。如果您创建一个新仓库,则无需重复这些步骤。

安装 JupyterLab

启动一个终端并输入以下命令安装 JupyterLab

conda install -c conda-forge -y jupyterlab

…或者

pip install jupyterlab

…如果您使用的是 pip 包管理器。

您现在可以输入以下命令启动 Jupyter

jupyter lab

这应该会在一个新的浏览器标签页中打开 JupyterLab

安装 nbdev

下一步是安装 nbdev 本身。JupyterLab 自带终端,所以我们将使用它进行后续操作。

在 Launcher 中,向下滚动到“Other”部分,然后点击“Terminal”。如果 Launcher 没有打开,您可以点击“File”→“New Launcher”来打开它。

应该会打开一个带有空白终端的新标签页——它可能看起来不完全一样,具体取决于您的 shell 配置

对于 Mac 和 Linux,输入

conda install -c fastai -y nbdev

…或者对于 Mac、Linux 和 Windows

pip install nbdev

…如果您使用的是 pip。

安装 Quarto

nbdev 提供了一个命令来安装最新版本的 Quarto。在终端中,输入

nbdev_install_quarto

此时可能会要求您输入密码。由于 nbdev 是开源的,您可以阅读此命令的源代码以验证它没有执行任何恶意操作。或者,如果您愿意,也可以选择遵循 Quarto 的官方安装说明

安装 Quarto JupyterLab 扩展

Quarto 提供了自己的 JupyterLab 扩展,使其能够渲染 Quarto markdown 内容。

例如,这是他们演示其部分功能的 notebook

通过输入以下命令安装该扩展

pip install jupyterlab-quarto

请注意,jupyterlab-quarto 包目前无法通过 conda 获取。


您已全部设置完毕并准备就绪!安装这些工具可能需要一些时间,但您只需执行一次。接下来,我们将为您的特定项目设置一个 nbdev 仓库。

第一步

到本节结束时,您将拥有自己的 nbdev 仓库,包含测试、持续集成、简化的 PyPI 和 conda 打包,以及一个文档网站。

创建一个空的 GitHub 仓库

使用方便的链接 github.com/new 创建一个空的 GitHub 仓库。如果您遇到困难,GitHub 的 创建仓库 页面可能会有所帮助。

请记住添加描述,因为 nbdev 稍后会用到它。暂时不要添加 README 文件、.gitignore 或 license。

如果您使用的是网页界面,在您点击“Create Repository”之前,它应该看起来像这样(带有您自己的仓库名称和描述)

然后您应该会被重定向到您的新仓库

GitHub 的网页界面是入门的好方法。随着您经验的增长,您可能想探索 GitHub CLI(命令行界面)。对于我们容易出错的重复性任务,我们通常更喜欢使用命令行工具。将这些任务编写成终端中的小型脚本意味着您可以轻松地重复它们。

使用 nbdev 初始化你的仓库

现在从您之前启动的 Jupyter 终端中克隆您的仓库(如果需要,请按照那些说明创建一个新的终端)。如果您在这里遇到困难,GitHub 的 克隆仓库 页面可能会有所帮助。

由于我们使用 fastai 用户创建了一个名为 nbev-hello-world 的仓库,我们可以按如下方式克隆它

git clone https://github.com/fastai/nbdev-hello-world.git

然后 cd(改变目录)到我们的仓库

cd nbdev-hello-world

克隆时您可能看到了这条消息

You appear to have cloned an empty repository.

…因为仓库完全是空的。让我们添加一些文件吧!

nbdev 提供了 nbdev_new 命令来初始化一个空的 git 仓库。它将从 git 和 GitHub 推断出有关您项目的信息,并要求您输入剩余的内容。它将在您的仓库中创建文件,这些文件将

  • 简化将 Python 包发布到 PyPI 和 conda 的流程。
  • 配置 Quarto 以生成出版级别的技术文档。
  • 设置 GitHub actions 以测试 notebooks 并构建 Quarto 文档并将其部署到 GitHub pages。

通过输入以下命令初始化您的 nbdev 仓库

nbdev_new

它可能会要求您输入无法从 git 或 GitHub 推断出的信息。

注意

nbdev_new 假定您的包名称与您的仓库名称相同(将 - 替换为 _)。如果不是这种情况,请使用 --lib_name 选项。

仔细检查您的 settings.ini 文件,确保所有信息都正确。然后提交并将您的添加推送到 GitHub

git add .
git commit -m'Initial commit'
git push

启用 GitHub Pages

nbdev 将您的文档托管在 GitHub Pages 上——一个优秀(且免费!)的网站托管方式。

注意

nbdev 默认使用 GitHub Pages,因为它易于访问。但是,您可以使用任何您喜欢的主机。有关更多信息,请参阅这些文档

您需要通过点击仓库页面右上角的“Settings”选项卡,然后点击左侧的“Pages”,将“Branch”设置为“gh-pages”,最后点击“Save”来为您的仓库启用 GitHub Pages。

点击“Save”后,它应该看起来与此类似

如果您没有看到“gh-pages”分支,请等待几分钟并重新加载页面。它应该会自动为您设置好。

现在是时候看看 nbdev 为您提供了哪些好东西了!

查看你的工作流

通过点击仓库页面顶部的“Actions”选项卡打开 GitHub Actions。您应该看到两个工作流运行

如果您在推送首次提交后不久打开此页面,运行可能还没有绿色的勾(✅),因为它们仍在“进行中”或“排队中”。没关系,它们完成所需的时间不应超过一分钟。

如果您看到一个红色的叉(❌),这意味着某个地方失败了。点击叉,然后点击“Details”,您将能够看到失败的原因。如果您无法弄清楚哪里出了问题,请搜索论坛,看看其他人是否解决了相同的问题,否则请创建一个新帖子,尽可能详细地描述您的问题,我们将尽力帮助您。请记住,提供实际仓库和/或 GitHub Action 的链接是帮助我们快速识别问题所在的最佳方式。

这些工作流有什么作用?

  • CI – CI(持续集成)工作流简化了您的开发者工作流程,尤其是在有多个协作者时。每次您推送到 GitHub 时,它都会确保
    • 您的 notebooks 和库是同步的
    • 您的 notebooks 已清除不必要的元数据(这些元数据会污染拉取请求和 git 历史记录并导致合并冲突)
    • 您的 notebook 测试全部通过
  • 部署到 GitHub Pages – 使用 Quarto 构建您的文档并将其部署到 GitHub Pages。

我们提供了这些开箱即用的基础工作流,但是您可以根据自己的喜好编辑 .github/workflows/ 文件夹中对应的 YAML 文件。

查看你的文档

当您启用 GitHub Pages 时,您应该会看到一个新的工作流运行:“pages build and deployment”。顾名思义,此工作流会将您的网站内容部署到 GitHub Pages。

等待工作流运行完成,然后打开您的网站。默认情况下,它应该可以在:https://{user}.github.io/{repo} 访问。例如,您可以查看 fastainbdev-hello-world 文档,地址是 https://fastai.github.io/nbdev-hello-world

总结

您现在有了一个基础的 nbdev 仓库,包含持续集成和托管文档!以下是您采取的步骤总结

  • 创建了一个 GitHub 仓库(并启用了 GitHub Pages)
  • 使用nbdev_new 初始化了您的仓库
  • 推送到 GitHub。

进行你的首次编辑

在本节中,您将对您在第一步中创建的仓库进行首次编辑。

安装 hooks 以获得对 git 友好的 notebooks

在新的仓库中使用 Jupyter notebooks 的第一步是安装 nbdev 的 hooks(您可以将“hooks”视为应用程序的插件或扩展)。

通过在终端中输入此命令来安装它们

nbdev_install_hooks
注意

Clean hook 当前仅支持 Jupyter Notebook 和 JupyterLab。如果您使用的是 VSCode,可以尝试 实验性的 nbdev VSCode 扩展。此外,您可能还想尝试 nbdev 的 pre-commit hooks

有关 nbdev hooks 工作原理以及如何自定义它们的更多信息,请参阅Git 友好的 Jupyter。以下是一个简短总结

  • 修复由于 git 合并冲突导致的损坏 notebooks,以便可以直接在 Jupyter 中打开和解决它们。
  • 每次保存 Jupyter notebook 时,自动清理不必要的元数据,以消除拉取请求中的不必要更改并减少 git 合并冲突的可能性。
  • 自动信任仓库中的 notebooks,以便您可以查看协作者提交中的 widgets。因此,您不应在不信任的仓库中安装 hooks
提示

nbdev 的 git hooks 适用于任何 git 仓库,即使它没有使用更广泛的 nbdev 系统。

构建你的库

您现在应该通过运行以下命令从您的 notebook 创建您的包

nbdev_export

这将为您的 notebooks 创建 Python 模块。这些模块将构成您的 Python 包的内容。

安装你的包

您可能已经注意到,nbdev_new 在您的仓库中创建了一个 Python 包。在我们的例子中,它通过使用我们的仓库名称 nbdev-hello-world 并将 - 替换为 _ 来自动命名为 nbdev_hello_world,使其成为一个有效的 Python 包。

下一步是将此命令输入您的终端来安装您的包

pip install -e '.[dev]'

这是使 Python 包在您当前环境中随处可导入的推荐方式

  • -e – “editable” 的缩写,允许您立即使用对您的包所做的更改,而无需重新安装,这对于开发非常方便。
  • . – 指代当前目录。
  • [dev] – 包括“development”(开发)依赖:您的 notebooks 仅用于文档或测试的其他包。

预览你的文档

nbdev 是一个重视快速反馈循环的交互式编程环境。nbdev_preview 命令通过使用 Quarto 在您的计算机上渲染文档并随着您编辑 notebooks 进行更新来帮助实现这一点。

通过在终端中输入此命令来启动预览

nbdev_preview

它可能在启动时显示几秒钟 Preparing to preview,并最终显示如下内容

Watching files for changes
Browse at http://localhost:3000/

点击链接在新浏览器标签页中打开预览。它应该看起来与您的在线文档完全一样。

提示

我们经常发现在 Jupyter 中编辑 notebooks 时,在旁边保持一个预览窗口打开非常有用。

编辑 00_core.ipynb

现在,在 Jupyter 中打开 nbs/00_core.ipynb 文件(由之前运行 nbdev_new 生成)。您不一定需要用数字开头命名 notebooks,但我们发现这样做有助于展示项目的阅读顺序——即使它们的创建顺序可能不同。

添加你自己的 frontmatter

您会看到类似这样的内容

core

在此填写模块描述

#| default_exp core

我们来解释一下这些特殊单元格的含义

  • 第一个是一个 markdown 单元格,使用了 nbdev 的 markdown frontmatter 语法,它定义了 Quarto(我们的文档引擎)使用的 notebook 元数据(有关更多信息,请参阅 frontmatter 参考页面)。它包含
    • H1 标题(“core”)——定义页面标题
    • 引用块(“在此填写模块描述”)——定义页面描述
  • 第二个是包含一个指令 default_exp 的代码单元格,它决定了此 notebook 将导出到哪个模块(有关更多信息,请参阅指令解释)。目前,它导出到 core 模块。

接下来,重命名 notebook,替换标题和描述,并更改您自己项目的默认导出模块。

完成后,保存 notebook。上一节中启动的实时预览应该会随着您的最新更改而更新。

重新运行 notebook 中的所有单元格,以确保它们正常工作,并导出更新后的模块。

提示

我们发现“restart kernel and run all cells”(重新启动内核并运行所有单元格)这个 Jupyter 命令(⏩ 按钮)非常宝贵,以至于我们将其绑定到了键盘快捷键。对 notebooks 的一个常见批评是,乱序执行会导致不可重现的 notebooks。根据我们的经验,养成“重启并重运行”的习惯可以解决这个问题。

运行 notebook 会导出 Python 模块,这是因为最后一个单元格包含了

#| hide
import nbdev; nbdev.nbdev_export()

这是什么意思?

  • #| hide 是一个指令(类似于 #| default_exp),它将一个单元格从您导出的模块和文档中排除。
  • nbdev_export 是用于将您的 notebooks 导出为 Python 模块的命令。

我们建议在所有您想导出的 notebook 的底部包含这样一个单元格。

警告

请记住删除任何未被 notebook 导出或您的包不需要的未使用模块。如果您更改了 notebook 的默认导出,很可能会发生这种情况——nbdev 不会删除旧模块。这是有意为之的,因为 nbdev 被设计用于处理混合包,这些包既使用 .py 模块(没有对应的 notebook),也使用从 notebooks 导出的模块。

添加你自己的函数

#| default_exp 单元格下方添加一个新的代码单元格,其中包含一个函数。例如

#| export
def say_hello(to):
    "Say hello to somebody"
    return f'Hello {to}!'

请注意,它在顶部包含 #| export——这是一个指令(类似于 #| default_exp),告诉 nbdev 将此单元格包含在您导出的模块和文档中。

文档应该看起来像这样


say_hello

 say_hello (to)

向某人打招呼

添加你自己的示例、测试和文档

notebook 驱动开发的一个超能力是,您可以非常轻松地在您的代码下方添加示例、测试和文档。

包含常规代码单元格,它们将(带输出)出现在您的文档中,例如

say_hello("Isaac")
'Hello Isaac!'

这也是一个测试!当您运行nbdev_test时,它将执行此单元格(以及所有其他测试单元格),如果它们引发任何异常则失败。

对于测试,更推荐使用更明确的 assert

assert say_hello("Hamel") == "Hello Hamel!"

…或者来自 fastcore.test 的函数,它们行为类似 assert,但在值不同时也会显示实际值和期望值

from fastcore.test import *
test_eq(say_hello("Hamel"), "Hello Hamel!")

notebook 驱动开发的另一个超能力是,您的示例可以包含图表、图像,甚至 JavaScript widgets。例如,这是一个 SVG 圆

from IPython.display import display,SVG
display(SVG('<svg height="100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="40"/></svg>'))

准备你的更改

在将更改提交到 GitHub 之前,我们建议在终端中运行 nbdev_prepare,它打包了以下命令

  • nbdev_export: 从 Jupyter notebooks 构建 .py 模块
  • nbdev_test: 测试您的 notebooks
  • nbdev_clean: 清理您的 notebooks 以去除冗余输出,方便 git 管理
  • nbdev_readme: 从您的 index notebook 更新仓库的 README.md 文件。

编辑 index.ipynb

现在您准备好个性化您的文档主页和 README.md 文件了;它们都是从 index.ipynb 自动生成的。打开 Jupyter,然后点击 nbs/index.ipynb 打开它。

我们建议包含更长的描述,说明您的包的功能、安装方法以及使用方法(附带一些导入和使用您的包的示例)。请记住,示例可以是带有实际输出的代码单元格,而不是纯 markdown 文本——它们也可以兼作测试!

推送到 Github

您现在可以提交并推送您的更改到 GitHub。如前所述,提交之前请务必运行 nbdev_prepare,以确保您的模块已导出且测试通过。您可以使用 git status 查看生成或更改了哪些文件。然后

git add .
git commit -m 'Add `say_hello`; update index' # Update this text with your own message
git push

这将触发您的 GitHub Actions。等待一两分钟让它们完成,然后检查您更新后的仓库和文档。

总结

恭喜您,您已经掌握了使用 nbdev 构建出色项目所需的所有基础知识!以下是您采取的步骤总结

  • 使用nbdev_install_hooks 安装了对 git 友好的 notebooks 的 hooks
  • 使用 pip install -e '.[dev]' 安装了您的包
  • 使用nbdev_preview 预览了您的文档
  • nbs/00_core.ipynb 添加了您自己的 frontmatter、函数、测试和文档
  • 使用 nbdev_prepare 准备了您的更改
  • 使用您自己的信息更新了 nbs/index.ipynb
  • 推送到 GitHub。

继续阅读以了解更多 nbdev 高级功能。另请参阅我们的解释,深入了解特定主题,以及我们的其他教程

高级功能

添加一个类

00_core.ipynb 中创建一个类,如下所示

#| export
class HelloSayer:
    "Say hello to `to` using `say_hello`"
    def __init__(self, to): self.to = to
        
    def say(self):
        "Do the saying"
        return say_hello(self.to)

这将自动在文档中显示如下


HelloSayer

 HelloSayer (to)

使用 say_helloto 打招呼

使用 show_doc 生成文档

然而,方法不会自动生成文档。要添加方法文档,请使用show_doc

show_doc(HelloSayer.say)

HelloSayer.say

 HelloSayer.say ()

执行打招呼的操作

并添加一些示例和/或测试

o = HelloSayer("Alexis")
o.say()
'Hello Alexis!'

设置 autoreload

由于您经常会在一个 notebook 中更新模块,并在另一个 notebook 中使用它们,如果您的 notebook 在 Python 文件更改后立即自动重新加载新模块,那将非常有帮助。要实现这一点,只需将以下行添加到您的 notebook 顶部

%load_ext autoreload
%autoreload 2

设置先决条件

如果您的模块需要其他模块作为依赖项,您可以将这些先决条件添加到您的 settings.ini 文件中的 requirements 部分。依赖项应以空格分隔,并且如果模块需要至少或至多某个特定版本的依赖项,也可以在此处指定。

例如,如果您的模块需要至少版本 1.0.5 的 fastcore 模块,至多版本 0.7 的 torchvision 模块以及任意版本的 matplotlib,那么先决条件将如下所示

requirements = fastcore>=1.0.5 torchvision<0.7 matplotlib

除了 requirements,您还可以使用其他具有不同范围的关键字指定依赖项。以下是所有可能的依赖项关键字列表

  • requirements: 传递给 pip 和 conda 设置
  • pip_requirements: 仅传递给 pip 设置
  • conda_requirements: 仅传递给 conda 设置
  • dev_requirements: 作为开发依赖传递给 pip 设置

有关依赖项格式的更多信息,请参阅 pypi 和 conda 关于在 setup.pymeta.yaml 中创建规范的文档。

设置控制台脚本

在幕后,nbdev 使用标准的 setuptools 包来处理模块安装。setuptools 的一个非常实用的功能是它可以自动创建跨平台控制台脚本。nbdev 将此功能呈现出来;要使用它,请使用与 setuptools 相同的格式,并在每个脚本定义之间使用空格(如果您有多个脚本定义)。

console_scripts = nbdev_export=nbdev.cli:nbdev_export

上传到 pypi

如果您希望人们只需输入 pip install your-project 就能安装您的项目,那么您需要将其上传到 pypi。好消息是,我们已经为您的项目创建了一个完全符合 pypi 规范的安装程序!所以您所需要做的就是,如果您之前没有在 pypi 注册过,请先注册(在 pypi 上点击“Register”),生成一个 API token(前往Account settings 并点击“Add API token”),然后创建一个名为 ~/.pypirc 的文件,其中包含您的 token 详细信息。它应该包含以下内容

[pypi]
username = __token__
password = your_pypi_token

您还需要 twine,所以应该运行一次

pip install twine

要将您的项目上传到 pypi,只需在您的项目根目录中输入 nbdev_pypi。完成后,将显示您的项目在 pypi 上的链接。

上传到 conda

类似于 pip install 支持,我们提供了一个符合 anaconda 规范的安装程序,用于将您的项目上传到 anaconda。上传后,您的包可以通过输入 conda install -c your_anaconda_username your-project 来安装。

您需要在 anaconda 注册(填写表格以 Sign Up),这将创建一个用户名和密码。然后您需要安装以下包

pip install anaconda-client conda-build conda-verify

在运行 anaconda 上传器之前,您需要使用 CLI 命令登录到 conda(系统将提示您输入用户名和密码)

anaconda login

要上传到 anaconda,只需在您的项目根目录中输入 nbdev_conda

同时上传到 pypi 和 conda

在 nbdev 仓库根目录运行命令 nbdev_release_both 将您的项目同时上传到 conda 和 pypi。

安装可折叠标题和 toc2 扩展

在使用此类项目时,我强烈推荐两个 jupyter notebook 扩展。它们是

  • 可折叠标题 (Collapsible headings): 这允许您根据 markdown 标题折叠和展开 notebook 中的每个部分。您还可以按 左箭头 键转到部分的开头,按 右箭头 键转到部分的末尾
  • TOC2: 这会向您的 notebooks 添加一个目录,您可以使用它添加到 notebook 中的 Navigate 菜单项或它添加的 TOC 侧边栏进行导航。这些可以通过其设置进行修改和/或隐藏。

支持数学公式

nbdev 支持数学公式(使用 Quarto)。您可以使用以下方法在 notebook 的文档中包含数学公式。

使用 $$,例如

\sum_{i=1}^{k+1}i

渲染后如下所示

_{i=1}^{k+1}i

使用 $,例如

This version is displayed inline: \sum_{i=1}^{k+1}i . You can include text before and after.

渲染后如下所示

此版本以内联方式显示: _{i=1}^{k+1}i 。您可以在其前后包含文本。

有关更多信息,请参阅 Quarto 文档

查看 nbdev“源码”以获取更多想法

别忘了 nbdev 本身就是用 nbdev 写的!这是一个很好的参考,可以看看 fast.ai 在实践中如何使用它,并获得一些技巧。您可以在 Github 的 nbs 文件夹中找到 nbdev 的 notebooks。

Quarto 功能

nbdev 支持大多数 Quarto 功能。我们鼓励您阅读 Quarto 文档,以发现所有可用的功能。例如,您可以整合 Graphviz

值得一看的是关于图表标注markdown小部件布局条件内容quarto 扩展的文档,这些是我们遇到的一些有用的东西。