nbdev:所有的事情都用木星笔记本

“我真的认为[nbdev]是一个巨大的进步的编程环境”:克里斯·拉特纳,迅速,LLVM,和操场斯威夫特的发明者。

我的同事fast.ai西尔Gugger在过去的几年里,我一直在做一项很有兴趣的工作。它是一个Python编程环境nbdev,它允许您创建完整的Python包,包括测试和丰富的文档系统,在所有Jupyter笔记本电脑。我们已经写了大量的编程库(fastai V2)采用nbdev,以及一系列较小的项目。

Nbdev是的东西,我们称之为系统探索性的编程。探索性编程是基于这样一种观察,即我们大多数人都把大部分时间花在了探索和实验上。我们尝试使用一个以前没有使用过的新API,以准确理解它的行为。我们探索我们正在开发的算法的行为,看看它是如何处理各种数据的。我们尝试通过探索输入的不同组合来调试代码。等等……

  1. nbdev:探索式编程
  2. 软件开发工具
  3. 交互式编程环境
  4. 那么是什么缺少Jupyter笔记本?
  5. 动态的Python
  6. 现在

nbdev:探索式编程

我们相信探索的过程本身是有价值的,这个过程应该被保存下来,以便其他程序员(包括你自己)可以看到发生了什么,并通过例子学习。你可以把它想象成一本科学家杂志。你可以用它来展示你尝试过的东西,哪些有效,哪些无效,以及你做了什么来加深你对你所工作的系统的理解。在探索过程中,您将认识到这种理解的某些部分对于系统的工作是至关重要的。因此,探索应该包括添加测试和断言,以确保这种行为。

当你开发上的提示(或REPL),或使用面向笔记本电脑的开发系统,如Jupyter笔记本电脑这种“探索”的是最容易。但是,这些系统不强,为“规划”的一部分。这就是为什么人们使用这种系统主要用于早期的探索,然后切换到一个项目的IDE或文本编辑器之后。他们改用得到这样良好的文档查找,良好的语法高亮,单元测试的集成,和(批判!)将产生最终的,分布式的源代码文件的能力的特性,而不是笔记本电脑或REPL历史。

nbdev的目的是将IDE/编辑器开发的关键好处引入到笔记本系统中,这样你就可以在笔记本上工作而不影响整个生命周期。为了支持这种探索,nbdev被构建在其之上Jupyter笔记本(这也意味着,我们得到更好的支持Python的动态特性,并为软件开发添加了以下非常重要的工具:

这是我们从实际的片段“源代码”为nbdev,这本身就是写在nbdev(当然!)

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

正如你所看到的,当你开发软件这样一来,每个人都在你的项目团队到达受益于你建立的问题域,如文件格式,性能,API边缘情况,等等的理解做的工作。由于发展在笔记本上出现,你也可以添加图表,文字,链接,图片,视频等,将图书馆的文件中自动被包括在内。在您的代码定义的细胞将被隐藏,并通过你的函数的标准化文件,显示出其名称,参数,文档字符串,并链接到GitHub上的源代码代替。

有关功能、安装和如何使用nbdev的更多信息,请参见它的文档(这是自然地,自动地从其源代码生成的)。我将在接下来的几天里发布一个循序渐进的教程。在这篇文章的其余部分,我将描述更多的历史和背景背后为什么为什么是我们造的吗为什么我们是按照我们的方式设计的吗?首先,让我们来谈谈一些历史…(如果你对历史不感兴趣,你可以跳到。朱佩特的笔记本里少了什么。)

软件开发工具

大多数软件开发工具都不是建立在探索式编程的基础之上的。大约30年前,当我开始编码时,瀑布式软件开发几乎被完全使用。在我看来,在当时,这种做法,在整个软件系统将在微小的细节前期被定义,然后尽可能接近编码的规范越好,根本不合身着如何事实上,我所做的工作。

在20世纪90年代,然而,事情开始发生变化。敏捷开发走红。人们开始明白现实,大多数软件开发是一个迭代过程,并开发了尊重这一事实的工作方式。但是,我们并没有看到软件开发工具,我们使用的主要变化,即相匹配的重大改变我们的工作方式。有工具的一些作品,其得到了加入我们的核武库,特别是围绕能够做测试驱动开发更容易。然而,这种工具似乎只是对现有编辑器和开发环境的小扩展,而不是真正地重新考虑开发环境应该是什么样子。

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

传奇高德纳走在时代的前列他想看到事情做得非常不同。1983年,他开发了一种叫做文学编程。他将它描述为一种结合了编程语言和文档语言的方法,从而使程序比只用高级语言编写的程序更健壮、更可移植、更容易维护,而且可以说编写起来更有趣。其主要思想是把程序当作一篇文学作品来对待,它是写给人类的,而不是写给计算机的。”很长一段时间,我被这个想法迷住了,但不幸的是它从来没有真正去任何地方。可用这种方式工作的工具导致了软件开发走更长的时间,只有极少数人决定,这种妥协是值得的。

近30年后,一个又一个辉煌的和革命的思想家,布雷维克多,表达了他对当前一代的开发工具深深的不满,并说明如何设计“一个编程系统的理解程序”。正如他在他的开创性演讲中所说"发明了关于原则”:“我们目前的计算机程序是什么概念—文本列表定义你的手直接从Fortran编译器—的派生和阿果50年代末。这些语言是为打孔卡设计的。

他摆出来,以充分合作的例子,一系列的设计编程系统的新原理说明。虽然没有人尚未完全落实所有他的想法,已经有实现它们的某些部分有些显著的尝试。也许最知名的和完整的执行情况,包括中间结果内嵌显示是克里斯·拉特纳的斯威夫特和Xcode中操场

在Xcode Playgounds示范
在Xcode Playgounds示范

虽然这是一个很大的进步,但它仍然受到了基本的限制,即坐在一个开发环境中,而这个开发环境最初并没有考虑这样的探索。例如,探索过程根本就没有被捕获,测试不能直接集成到其中,而文字编程的完整丰富愿景也不能被实现。

交互式编程环境

目前已在软件开发的另一种非常不同的方向,这是交互式编程(和相关的现场编程)。它始于几十年前与LISP和第四REPLs,允许开发者在程序运行时交互添加和删除代码。Smalltalk中拿了东西更进一步,提供了完全互动的可视工作区。在所有这些情况下,语言本身是非常适合于这种互动的工作,例如与LISP的宏观制度和“代码数据”的基础。

Smalltalk的现场直播(1980)
Smalltalk的现场直播(1980)

虽然这种方法是不是最正规的软件开发今天怎么做,这是在科学,统计和其他数据驱动式编程的许多领域中最流行的方法。(JavaScript的前端编程然而越来越多地从这些方法,如热重新加载和浏览器实时编辑借用的想法。)Matlab举例来说,开始是在1970年的一个完全互动的工具回来,今天仍然被广泛应用于工程,生物等各个领域(它也提供定期的软件开发功能现在)。类似的方法被SPLUS使用,并且它是开源的表妹[R,这是今天在统计和数据可视化社区非常受欢迎(其中包括)。

当我第一次使用时,我特别兴奋Mathematica大约25年前。在我看来,Mathematica是我所见过的最接近于支持文字编程的软件,而又不会降低工作效率。为此,它使用了一个“笔记本”界面,其行为与传统的REPL非常相似,但也允许包含其他类型的信息,包括图表、图像、格式化的文本、概述部分等等。事实上,它不仅没有降低工作效率,而且我发现它实际上允许我构建以前无法完成的东西,因为我可以尝试算法,并立即以一种非常直观的方式得到反馈。

In the end though, Mathematica didn’t really help me build anything useful, because I couldn’t distribute my code or applications to colleagues (unless they spent thousands of dollars for a Mathematica license to use it), and I couldn’t easily create web applications for people to access from the browser. In addition, I found my Mathematica code would often end up much slower and more memory hungry than code I wrote in other languages.

所以你可以想象我的兴奋,当Jupyter笔记本出现在现场。这使用相同的笔记本基本接口数学(虽然,起初,随着功能的一小部分),但开源的,让我在得到广泛的支持,并免费提供语言编写。我一直在使用Jupyter不只是为了探索算法,API和新的研究思路,同时也为在fast.ai.教学工具许多学生发现,能力与投入试验,并查看中间结果和输出,以及尝试自己修改,帮助他们更全面和深入地了解正在讨论的主题。

我们也写书完全使用Jupyter笔记本,这是一种绝对的乐趣,允许我们组合散文、代码示例、分层结构的标题,等等,同时确保我们的示例输出(包括图表、表格和图像)始终与代码示例正确匹配。

简而言之:我们真的很喜欢使用木星笔记本,我们发现我们用它做的很好,我们的学生喜欢它。但是,我们实际上并没有使用它来构建我们的软件,这看起来真是太遗憾了!

那么是什么缺少Jupyter笔记本?

虽然Jupyter笔记本是“探究性节目”的“探究性”部分的部分大,它没有那么大的“规划”的一部分。例如,它并没有真正提供一种方法做这样的事情:

正因为如此,人们通常不得不在一种集成很差的工具之间切换,在他们从一种工具移动到另一种工具的过程中会有很大的摩擦,以获得每种工具的优势:

发展 优点 缺点
IDE /编辑器
  • 产生最终的分配模块
  • 集成文档查找
  • 集成语法高亮和类型检查
  • 非交互式,所以努力探索
  • 不完全支持动态语言
  • 文档是文本
  • 没有记录交互会话或通过示例进行解释的工具
REPL /壳
  • 适合小的互动探索
  • 坏的一切,包括生产分配模块
传统的笔记本电脑
  • 混合代码,丰富的文本和图像
  • 通过记录的交互会话说明直通例子
  • 动态语言的精确代码导航和自动完成
  • 相同的缺点如REPL编程

我们决定来处理这些东西的最佳方式是利用强大的工具已经存在,如果可能的话,并建立我们自己的需要的地方。例如,对于处理引入请求,并查看差别,不过已经有了很好的工具:ReviewNB。当你看到在ReviewNB图形化的diff,你突然意识到有多少已经失踪这段时间以纯文本格式的diff。举例来说,如果一个提交了你的图像产生模糊?或使你的图表显示无标签?你真的知道是怎么回事,当你有视觉差异上。

ReviewNB中的可视差异,显示对表格输出的更改
ReviewNB中的可视差异,显示对表格输出的更改

许多合并冲突避免与nbdev,因为它为您安装混帐挂钩其中剔除多,导致摆在首位这些冲突的元数据。如果你得到一个合并冲突,当你从混帐拉,只需运行nbdev_fix_merge。使用此命令,nbdev将只使用您的如果单元输入中有冲突,则细胞被包含在最终的笔记本,冲突标记一起,所以你可以很容易地找到他们,并直接在Jupyter解决这些问题。

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

nbdev通过简单地创建标准Python模块来创建模块化可重用代码。nbdev在代码单元格中查找特殊注释,例如#出口,这表示单元格应该导出到python模块。通过在笔记本开始处使用一个特殊的注释,每个笔记本都与一个特定的python模块相关联。一个文档站点(使用变身怪医,因此直接支持GitHub页面)是自动建立从笔记本和特别的评论。我们编写了自己的文档系统,因为现有的方法(比如Sphinx)没有提供我们需要的所有特性。

对于代码的导航,已经有内置于大多数编辑器和IDE,比如VIM,Emacs和vscode奇妙的功能。作为奖励,甚至GitHub的支持直接代码导航在它的Web界面现在(处于测试阶段,在选定的项目,如fastai)!因此,我们已经确保了代码nbdev出口可以导航,直接在这些系统中编辑 - 和任何编辑可以自动同步与笔记本电脑回来。

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

动态的Python

在常规编辑器或IDE中完全支持Python的挑战之一是Python的功能特别强大动态特性。例如,您可以在任何时候向类添加方法,您可以通过使用元类系统,您可以通过使用。更改函数和方法的行为方式装饰。微软开发了语言服务器协议,这可以通过开发环境中使用,以获取有关当前文件的信息和项目所需的自动补全,代码导航,等等。However, with a truly dynamic language like python, such information will always just be guesses, since actually providing the correct information would require running the python code itself (which it can’t really do, for all kinds of reasons - for instance the code may be in a state while you’re writing it that actually deletes all your files!)

在另一方面,笔记本,包含一个实际运行Python解释器的实例,你在控制很充分。所以Jupyter可以提供自动补全,参数列表,并根据您的代码的实际状态上下文敏感的文档。例如,当使用熊猫我们得到我们DataFrames的所有列名的选项卡中完成。我们发现,Jupyter笔记本的这一功能使得探索式编程显著更富有成效。我们没有必要去改变什么,使其在nbdev正常工作;它是Jupyter的强大功能,我们通过构建一个平台上免费获得只是一部分。

现在

为了配合nbdev的开发,我们从头开始完全用nbdev编写fastai v2。fastai v2为构建深度学习模型提供了丰富的、结构良好的API。它将于2020年上半年发布。它的功能已经完成,早期的采用者已经用预发布版本构建了很酷的项目。我们还用fastai v2编写了其他项目,其中一些将在未来几周发布。

我们发现,我们使用nbdev比使用传统的编程工具是2倍到3倍的工作效率。对我来说,这是一个很大的惊喜,因为我几乎每天编码超过30年,而在当时曾试图几十工具,库和系统建设方案。我没想到有可能仍然是空间生产率的一项艰巨的任务。它让我觉得对未来充满期待,因为我怀疑有可能仍然是一个很大的发展空间,为开发人员的生产力其他的想法,因为我期待着看到什么人建立与nbdev。

如果您决定给它一个去,请让我们知道你是如何相处!当然还有随意问任何问题。这些讨论的最好的地方是这个论坛话题这是我们为nbdev创建的。PRs当然是受欢迎的nbdev GitHub库

感谢您参加我们项目的兴趣!


确认:感谢亚历克西斯Gallagher和维亚切Kovalevskyi他们在这个文章的草稿有帮助的反馈。感谢安德鲁·肖的帮助的构建原型show_doc,并向Stas Bekman介绍了大部分git挂钩功能。感谢Hamel Husain对GitHub行动的帮助。