nbdev :使用Jupyter Notebooks进行所有操作

“我确实认为[nbdev]对于编程环境而言是巨大的进步”: 克里斯·拉特纳 是Swift,LLVM和Swift 游乐场 的发明者。

我的fast.ai同事 西尔万·古格 最近几年,我一直在从事爱情工作。这是一个称为Python的编程环境 nbdev ,可让您创建完整的python程序包,包括测试和丰富的文档系统 Jupyter笔记本 。我们已经使用nbdev编写了一个大型编程库(fastai v2),以及一系列较小的项目。

Nbdev是一个我们称之为的系统 探索性编程。探索性编程基于以下观察结果:我们大多数人在编码人员进行探索和试验时会花费大部分时间。我们尝试使用以前未使用过的新API,以准确了解其行为。我们探索了正在开发的算法的行为,以了解其如何处理各种数据。我们尝试通过探索输入的不同组合来调试代码。依此类推...

  1. nbdev :探索性编程
  2. 软件开发工具
  3. 互动编程环境
  4. 那么Jupyter Notebook中缺少什么?
  5. 动态Python
  6. 现在怎么办

nbdev :探索性编程

我们认为,探索的过程本身就是很有价值的,应该省去这个过程,以便其他程序员(包括六个月的时间里的您自己)可以看到发生的事情并通过示例学习。可以把它想成是科学家的日记。您可以使用它来显示您尝试过的东西,有效的方法和无效的方法,以及为增进对工作系统的了解而进行的工作。在探索过程中,您将认识到这种理解的某些部分对于系统正常工作至关重要。因此,探索应包括添加测试和断言以确保这种行为。

当您根据提示(或REPL)进行开发,或使用面向笔记本的开发系统(如Jupyter Notebooks)时,这种“探索”是最容易的。但是这些系统对于“编程”部分而言并不那么强大。这就是为什么人们主要使用此类系统进行早期探索,然后在项目中稍后切换到IDE或文本编辑器的原因。他们切换到获得诸如良好的文档查找,良好的语法突出显示,与单元测试的集成以及(关键!)能够生成最终的,可分发的源代码文件(而不是笔记本或REPL历史记录)的功能。

nbdev 的要点是将IDE /编辑器开发的主要优点带入笔记本系统,因此您可以在笔记本中工作而不会损害整个生命周期。为了支持这种探索,nbdev建立在 Jupyter笔记本 (这也意味着我们获得了更好的支持 Python的动态功能 而不是在普通编辑器或IDE中),并为软件开发添加了以下至关重要的工具:

这是我们实际的“ 源代码 ”表示nbdev,它本身就是用nbdev编写的(当然!)

探索nbdev源代码中的笔记本文件格式
探索nbdev源代码中的笔记本文件格式

如您所见,当您以此方式构建软件时,项目团队中的每个人都将从您对问题域的理解(例如文件格式,性能特征,API边缘情况等)的理解中受益。由于开发是在笔记本中进行的,因此您还可以添加图表,文本,链接,图像,视频等,这些信息将自动包含在库的文档中。定义代码的单元格将被隐藏,并由函数的标准化文档替换,显示其名称,参数,文档字符串以及指向GitHub上源代码的链接。

有关功能,安装以及如何使用nbdev的更多信息,请参见 它的文件 (自然是从其源代码自动生成的)。我将在未来几天内发布分步教程。在本文的其余部分,我将详细介绍该主题背后的历史和背景。 为什么 : 为什么 我们建造了吗? 为什么 我们是否按照我们的方式设计了它。首先,让我们谈一些历史...(如果您对历史不感兴趣,可以跳至 Jupyter笔记本 中缺少什么

软件开发工具

大多数软件开发工具并不是基于探索性编程的思想构建的。大约30年前,当我开始编码时, 瀑布软件开发 被几乎专门使用。在我当时看来,这种方法需要先详细定义整个软件系统,然后再尽可能地按照规范进行编码,这与我实际完成工作的方式完全不符。

然而,在1990年代,情况开始发生变化。 敏捷发展 变得流行。人们开始理解现实,即大多数软件开发都是 迭代过程 ,并开发了尊重这一事实的工作方式。但是,我们没有看到所使用的软件开发工具发生重大变化,而这些变化与我们的工作方式没有发生重大变化。我们的武器库中增加了一些工具,尤其是在能够 测试驱动开发 更容易。但是,该工具往往是对现有编辑器和开发环境的次要扩展,而不是真正地重新考虑开发环境的外观。

近年来,我们也开始看到对 探索性测试 作为敏捷工具箱的重要组成部分。我们绝对同意!但是我们也认为这还远远不够。我们认为 每一个 在软件开发过程中,探索应该成为故事的中心部分。

传奇 唐纳德·克努斯 比他的时间领先。他希望看到事情做得截然不同。 1983年,他开发了一种方法,称为 识字编程 。 他 描述为 一种将编程语言与文档语言相结合的方法,与仅使用高级语言编写的程序相比,它使程序更健壮,更可移植,更易于维护,并且编写起来更有趣。主要思想是将程序视为文学作品,发给人类而不是计算机。 长期以来,我对这个想法着迷,但不幸的是,它从来没有真正实现过。可以通过这种方式使用的工具导致软件开发花费了更长的时间,很少有人认为这种妥协值得。

大约30年后,另一位杰出的革命思想家布雷特·维克托(Bret Victor)对当代的开发工具表达了深深的不满,并描述了如何设计“用于理解程序的编程系统”。正如他在开创性讲话中所说:原理发明 ”:“ 我们目前对计算机程序的概念是指您交给编译器的文本定义列表,这些概念直接源于上世纪50年代的Fortran和ALGOL。这些语言是为打孔卡设计的。

他列出了一系列用于设计程序设计系统的新原理,并通过充分工作的示例进行了说明。尽管还没有人完全实现他的所有想法,但是人们已经进行了一些重要的尝试来实现其中的某些部分。 克里斯·拉特纳 的Swift和Xcode也许是最著名,最完整的实现,包括中间结果的内嵌显示 游乐场 .

Xcode中的Playgounds演示
Xcode中的Playgounds演示

尽管这是向前迈出的一大步,但它仍然受到坐在开发环境中的基本局限性的限制,而开发环境最初并不是在考虑这种探索的情况下构建的。例如,探索过程根本没有被捕获,测试无法直接集成到其中,并且无法实现全面的识字编程视野。

互动编程环境

软件开发还有另一个非常不同的方向,那就是 互动程式 (及相关 现场节目 )。它始于数十年前的LISP和Forth REPL,它们允许开发人员在正在运行的应用程序中以交互方式添加和删除代码。 Smalltalk使事情变得更进一步,提供了完全交互式的可视工作区。在所有这些情况下,例如LISP的宏系统和“代码即数据”基础,这些语言本身都非常适合这种交互式工作。

Smalltalk中的实时编程(1980)
Smalltalk中的实时编程(1980)

尽管这种方法不是当今大多数常规软件开发的完成方式,但它是科学,统计和其他数据驱动的编程领域中最受欢迎的方法。 (但是,JavaScript前端编程越来越多地借鉴这些方法的想法,例如热重载和浏览器内部实时编辑。) Matlab的 的 例如,它最初是在1970年代最初是一种完全交互式的工具,如今仍广泛用于工程,生物学和其他各个领域(如今,它还提供常规的软件开发功能)。 SPLUS使用了类似的方法,并且它是开源的堂兄 R,今天在统计和数据可视化社区(以及其他社区)中非常流行。

我第一次使用时特别兴奋 Mathematica 大约25年前Mathematica在我看来就像我所见过的最接近可以支持识字编程但又不影响生产率的事物。为此,它使用了“笔记本”界面,该界面的行为与传统的REPL十分相似,但还允许包含其他类型的信息,包括图表,图像,带格式的文本,概述部分等。实际上,它不仅不影响生产力,而且我发现它实际上使我能够构建以前无法实现的功能,因为我可以尝试算法并立即以非常直观的方式获得反馈。

但是最后,Mathematica并没有真正帮助我建立任何有用的东西,因为我无法将我的代码或应用程序分发给同事(除非他们花了数千美元来获得使用它的Mathematica许可),而我却不容易创建供人们从浏览器访问的Web应用程序。另外,我发现我的Mathematica代码通常比用其他语言编写的代码要慢得多,而且占用的内存也更多。

因此,您可以想象Jupyter Notebook出现在现场时的激动。它使用了与Mathematica相同的基本笔记本界面(尽管起初只是功能的一小部分),但它是开源的,并允许我用受到广泛支持和免费使用的语言来编写。我一直在使用Jupyter,不仅用于探索算法,API和新的研究思路,而且还用作fast.ai的教学工具。许多学生发现,能够尝试输入并查看中间结果和输出以及尝试自己修改的能力,有助于他们更充分,更深入地了解所讨论的主题。

我们也是 写书 完全使用Jupyter Notebooks,这是绝对的荣幸,它使我们能够结合散文,代码示例,层次结构的标题等,同时确保我们的示例输出(包括图表,表格和图像)始终正确匹配代码示例。

简而言之:使用Jupyter Notebook非常愉快,我们发现使用Jupyter Notebook做得很好,我们的学生也喜欢它。但这似乎太可惜了,我们实际上并没有使用它来构建我们的软件!

那么Jupyter Notebook中缺少什么?

尽管Jupyter Notebook在“可探索编程”的“可探索”部分很出色,但在“编程”部分却不那么出色。例如,它实际上并没有提供执行以下操作的方法:

因此,人们通常必须在各种集成不良的工具之间进行切换,并且随着工具的使用而产生巨大的摩擦,才能获得每种工具的优势:

发展历程 优点 缺点
IDE /编辑器
  • 产生最终的可分配模块
  • 与文档查找集成
  • 与语法突出显示和类型检查集成
  • 非交互式,因此难以探索
  • 对动态语言的不完全支持
  • 文档仅为文本
  • 没有记录互动会话或通过示例解释的工具
REPL /壳
  • 适合小型互动探索
  • 不利于其他一切,包括生产可分发模块
传统笔记本
  • 混合代码,富文本和图像
  • 通过记录互动会话来解释示例
  • 动态语言的准确代码导航和自动完成
  • 与REPL编程相同的缺点

我们认为处理这些问题的最佳方法是在可能的情况下利用已经存在的出色工具,并在需要时构建自己的工具。例如,用于处理请求请求和查看差异的工具已经存在: 评论NB 。当您查看ReviewNB中的图形差异时,您突然意识到这些时间在纯文本差异中已经丢失了多少。例如,如果一次提交使您的图像生成模糊了怎么办?还是使图表显示为无标签?您确实知道发生视觉差异时发生了什么。

 评论NB 中的视觉差异,显示对表格输出的更改
评论NB 中的视觉差异,显示对表格输出的更改

Many merge conflicts are avoided with nbdev , because it installs git hooks for you which strip out much of the metadata that causes those conflicts in the first place. If you get a merge conflict when you pull from git, just run nbdev _fix_merge. With this command, nbdev will simply use 您的 单元输出中输出存在冲突的单元,如果单元输入中存在冲突,则 最终笔记本中包含单元格以及冲突标记,因此您可以轻松找到它们并直接在Jupyter中修复它们。

基于单元的nbdev合并冲突的示例
基于单元的nbdev合并冲突的示例

Modular reusable code is created by nbdev by simply creating standard Python modules. nbdev looks for special comments in code cells, such as #export, which indicates that a cell should be exported to a python module. Each notebook is associated with a particular python module by using a special comment at the start of the notebook. A documentation site (using 杰基尔 ,因此得到了直接支持 GitHub页面 )是根据笔记本和特殊注释自动生成的。我们编写了自己的文档系统,因为Sphinx等现有方法并未提供我们所需的所有功能。

对于代码导航,大多数编辑器和IDE中已经内置了许多很棒的功能,例如vim,Emacs和vscode。另外,GitHub甚至 直接支持代码导航 现在在其Web界面中(在Beta中,在某些项目中,例如fastai)!因此,我们确保可以在任何这些系统中直接导航和编辑nbdev导出的代码-并且可以进行任何编辑 自动同步 带笔记本回来。

为了进行测试,我们编写了自己的简单库和命令行工具。测试是直接在笔记本中编写的,这是探索和开发(和文档)过程的一部分,并且命令行工具在所有笔记本中并行运行测试。笔记本电脑的自然状态证明是开发单元测试和集成测试的一种非常好的方法。您无需使用特殊的语法来学习如何创建测试套件,而只需使用python中的常规集合和循环结构即可。因此,需要学习的新概念要少得多。这些测试也可以在您的常规连续集成工具中运行,并且它们提供有关出现的任何测试错误的来源的清晰信息。默认的nbdev模板包括与 GitHub动作 用于持续集成和其他功能(欢迎用于其他平台的PR)。

动态Python

在常规编辑器或IDE中完全支持Python的挑战之一是Python具有特别强大的功能。 动态 特征。例如,您可以随时将方法添加到类中,可以通过使用以下命令更改类的创建方式及其工作方式 元类系统 ,您可以通过使用更改函数和方法的行为 装饰工 。微软开发了 语言服务器协议,开发环境可以使用该工具来获取有关自动完成,代码导航等所需的当前文件和项目的信息。但是,对于真正的动态语言(如python),此类信息始终只是猜测,因为实际提供正确的信息将需要运行python代码本身(出于各种原因,它实际上并不能执行-例如代码可能在您实际上正在删除所有文件的状态下!)

另一方面,笔记本包含您完全可以控制的实际运行的Python解释器实例。因此,Jupyter可以根据代码的实际状态提供自动完成,参数列表和上下文相关的文档。例如,当使用 大熊猫 我们将获得DataFrames所有列名称的制表符补全。我们发现,Jupyter Notebook的此功能使探索性编程的工作效率大大提高。我们无需进行任何更改即可使其在nbdev中正常运行;这只是Jupyter强大功能的一部分,我们可以通过在该平台上免费获得它。

现在怎么办

在开发nbdev的过程中,我们一直在从头开始完全用nbdev编写fastai v2。 fastai v2提供了丰富的,结构良好的API,用于构建深度学习模型。它将在2020年上半年发布。它已经具有完整的功能,并且早期采用者已经在使用预发布版本构建出色的项目。我们还在fastai v2中编写了其他项目,其中一些项目将在未来几周内发布。

我们发现与传统编程工具相比,使用nbdev可以使效率提高2到3倍。对我来说,这是一个很大的惊喜,因为30年来我几乎每天都在编码,并且在那段时间里尝试了许多工具,库和系统来构建程序。我没想到在生产力方面还有这么大的工作空间。这让我对未来感到兴奋,因为我怀疑仍有很多空间可以开发其他想法来提高开发人员的工作效率,并且因为我期待看到人们使用nbdev构建的东西。

如果您决定尝试一下,请告诉我们您的相处方式!当然,请随时提出任何问题。这些讨论的最佳地点是 这个论坛主题 我们为nbdev创建的。公关当然是欢迎的 nbdev GitHub存储库 .

感谢您对我们的项目感兴趣!


致谢 : Thanks to Alexis Gallagher and Viacheslav Kovalevskyi for their helpful feedback on drafts of this article. Thanks to Andrew Shaw for helping to build prototypes of show_doc, and to Stas Bekman for much of the git hooks functionality. Thanks to Hamel Husain for helpful ideas for using GitHub动作 .