当前位置: 首页 > 科技观察

这是一个脱胎于JupyterNotebook的全新编程环境

时间:2023-03-21 15:21:43 科技观察

不久前,fast.ai创始研究员JeremyHoward写了一篇文章介绍了nbdev,这是fast.ai最近提出的一个基于JupyterNotebook的全新编程环境,以及将IDE编辑器的优势带入JupyterNotebook,可以在Notebooks中开发,不影响整个项目生命周期。nbdevGitHub地址:https://github.com/fastai/nbdev/nbdev文档:https://nbdev.fast.ai/“在我看来,nbdev是对编程环境的巨大改进。”-Swift、LLVM和SwiftPlaygrounds的创建者ChrisLattner近年来,我和我的同事SylvainGugger一直致力于我们热爱的事情:Python编程环境nbdev。nbdev允许用户在JupyterNotebooks中创建完整的Python包,包括测试和丰富的文档系统。我们已经编写了一个大型编程库(fastaiv2)以及几个使用nbdev的小项目。本文作者JeremyHoward,fast.ai创始研究员。nbdev系统适用于“探索性编程”。我们发现大多数程序员将大部分工作时间都花在了探索和试验上。例如,我们将尝试一个从未使用过的新API来了解它是如何工作的;我们探索我们正在开发的算法的行为,以了解它如何处理不同的数据类型;我们探索不同的输入组合来调试代码...nbdev:探索性编程我们认为探索过程是有价值的,应该保留,以便其他程序员(或我自己)可以在六个月内看到正在发生的事情并通过示例学习。将其视为一本科学期刊,您可以在其中展示您尝试过的内容(包括有效的和无效的),以及您为增加对工作系统的理解所做的努力。在探索过程中,你会发现你理解的某些部分对系统的运行至关重要,因此探索应该包括测试和断言(testsandassertions)。当您从提示(或REPL)或使用面向笔记本的开发系统(如JupyterNotebook)进行开发时,“探索”是最简单的。但是这些系统的“编程”部分并不那么强大。这就是为什么人们大多使用这些类型的系统进行早期探索,然后转向IDE或文本编辑器。切换到其他系统的原因是为了获得笔记本或REPL没有的功能,例如:良好的文档查找、良好的语法突出显示、集成的单元测试,以及(至关重要的)生成最终可分发源代码文件的能力。nbdev将IDE/编辑器开发的优势带入notebook系统,让用户可以在不影响整个项目生命周期的情况下,在notebook中完成开发。为了支持这种类型的探索,nbdev构建在JupyterNotebook之上(这意味着nbdev比普通编辑器或IDE更好地支持Python的动态特性),并添加了以下用于软件开发的重要工具:遵循最佳实践自动创建Python模块,例如使用导出的函数、类和变量自动定义__all__;在标准文本编辑器或IDE中执行代码导航和编辑,并自动将所有更改导出回笔记本;基于代码链接文档自动创建可搜索的超文本,引号中的任何词都超链接到相应的文档,文档站点的侧边栏有到每个模块的链接等;pip安装程序(上传到PyPI);测试(直接在笔记本中定义,可以并行运行);持续集成;版本控制和冲突处理。下图是nbdev真正源码的一个片段,是用nbdev写的。探索nbdev源代码中的笔记本文件格式。如上图所示,以这种方式构建软件时,项目团队的所有成员都可以从您为理解问题域所做的工作中受益,例如文件格式、性能特征、API边缘案例等。由于开发过程发生在笔记本中,您还可以添加图表、文本、链接、图像、视频等,这些内容将自动包含在库文档中。定义代码的单元格将被隐藏并替换为显示其名称、参数、文档字符串和源代码GitHub链接的规范化函数文档。有关nbdev功能、安装和使用的更多信息,请参阅nbdev文档:https://nbdev.fast.ai/。以下部分描述了构建nbdev的原因以及nbdev设计理念背后的历史和背景。首先,让我们了解一下历史。(如果您不感兴趣,请跳至“JupyterNotebook缺少什么?”)软件开发工具大多数软件开发工具在创建时都没有考虑探索性编程。大约30年前,当我第一次开始编码时,瀑布式软件开发几乎是一种垄断。这种编程方法预先详细定义整个软件系统,然后尽可能接近规范对其进行编程。当时,我认为这种方法不适合我的工作方式。在20世纪90年代,情况发生了变化,敏捷开发开始流行。人们开始了解大部分软件开发是一个迭代过程的现实,并开发出符合这一事实的工作方式。然而,我们当时使用的软件开发工具并没有完成与我们工作方式变化相匹配的变化。库中添加了几个工具,以便更轻松地执行测试驱动开发。但这些工具只是对现有编辑器和开发环境的轻微扩展,并没有真正重新思考开发环境应该是什么样子。探索性测试是敏捷测试的重要组成部分,近年来人们对探索性测试的兴趣越来越大。我们完全同意这一点,但我们认为我们做得还不够。我们相信探索应该是软件开发过程每个部分的核心。传奇人物DonaldKnuth超前于他的时代,并希望看到它以不同的方式发展。1983年,他提出了一种称为“文学编程”的方法,他将这种方法描述为“结合编程语言和文档语言,这样可以编写出比单独使用高级语言编写的程序更健壮和可靠的程序。“便携,更容易维护,写起来更有趣。主要思想是把程序看成文学,而不是计算机。”很长一段时间我都对这个想法很着迷,但不幸的是这个想法没有实现。因为这会导致软件开发时间变长,没有人愿意付出这个代价。将近30年后,另一位变革性思想家布雷特维克多表达了对当时开发工具的深深不满,并描述了如何设计“理解程序的编程系统”。在他突破性的演讲“Inventingon原则,”他说:“我们当前的计算机程序概念是一串文本定义,您在1950年代后期将其传递给直接基于Fortran和ALGOL的编译器。但是Fortran和ALGOL语言是为穿孔卡设计的。”他展示了行之有效的例子,以及编程系统设计的几个新原则。虽然没有人完全实现他的所有想法,但有些人已经尝试实现一些他们中的。也许最著名和最完整的实现(包括中间结果的演示)是由ChrisLattner创建的Swift和XcodePlaygrounds。XcodePlaygrounds的演示图。虽然这是一个重要的飞跃,但仍然受到开发环境不是为此类探索构建的根本限制。比如开发环境无法捕捉探索过程,测试无法直接集成到开发环境中,无法实现文学编程的完美版本。交互式编程环境软件开发的一个不同方向是交互式编程(以及相关的实时编程)。几十年前就出现了交互式编程的尝试,例如LISP和ForthREPL,它们允许开发人员以交互方式在正在运行的应用程序中添加和删除代码。Smalltalk通过提供完全交互式的可视化工作区,更进一步。在所有这些情况下,语言本身非常适合交互式工作方式,例如LISP的宏系统和“代码即数据”基础。Smalltalk语言实时编程(1980)。今天,这种方法不是最传统的软件开发方法,但在科学、统计和其他数据驱动编程等多个领域是最流行的。(JavaScript前端编程继续借鉴这些方法的思想,例如热重载和浏览器内实时编辑。)例如,Matlab在1970年代作为一种完全交互的工具开始,至今仍广泛用于工程、生物学,等等(目前还提供通用的软件开发功能)。类似的方法已用于S-PLUS,与S-PLUS相关的开源语言R目前在统计和数据可视化社区中非常流行。25年前,当我第一次使用Mathematica时,我感到非常兴奋。对我来说,Mathematica是最有可能在不影响生产力的情况下支持文学编程的语言。Mathematica使用“笔记本”界面,其行为类似于传统的REPL,但允许其他类型的信息,例如图表、图像、格式化文本、概述部分等。事实上,它不仅没有影响生产力,我还用它来构建东西我以前无法构建的。它帮助我在试验算法后立即获得视觉反馈。最后,Mathematica没有帮助我构建任何有用的东西,因为我无法将我的代码或应用程序分发给同事(除非他们为Mathematica许可证支付了数千美元),而且我无法轻松创建浏览器可读的Web应用程序。此外,我发现Mathematica代码通常比用其他语言编写的代码更慢并且占用更多内存。所以你可以想象当JupyterNotebook诞生时我是多么的兴奋。JupyterNotebook具有与Mathematica相同的基本笔记本界面(尽管最初JupyterNotebook的界面只有后者的一小部分功能),并且它是开源的,因此我可以使用广泛支持和免费提供的语言编写代码。我使用Jupyter探索算法、API和新的研究思路,并将其作为fast.ai的教学工具。许多学生发现,能够对输入进行实验、查看中间结果和输出并进行修改,有助于他们对所讨论的主题形成更完整、更深刻的理解。我们还使用JupyterNotebook写了一本书,这很有趣。基于JupyterNotebook,我们在书中结合了散文、代码示例、层次结构的标题等,同时确保示例输出(包括图表、表格和图像)与代码示例完美匹配。简而言之:我们非常喜欢使用JupyterNotebook并用它完成了出色的工作,学生们也喜欢它。但是我们不能用它来构建我们自己的软件!Jupyter笔记本缺少什么?JupyterNotebook擅长“探索性编程”的“探索性”部分,但不太擅长“编程”。例如,它没有提供一种方法来:创建可以在Jupyter之外运行的模块化可重用代码;创建可搜索的超链接文档;测试代码(包括通过持续集成进行的自动化代码测试);代码导航;版本控制。因此,开发人员通常需要在未很好集成的工具之间切换才能获得这些工具的优势,而在工具之间来回切换可能会导致冲突。不同工具的优势如下:我们认为,处理这些冲突的最佳方法是使用现有的好工具构建所需的功能。例如,对于处理拉取请求和查看差异,已经有一个很棒的工具:ReviewNB。当你在ReviewNB中查看图形版本的diff时,你会突然发现纯文本diff中缺少信息。例如,如果提交模糊了图像生成结果,或者使图表没有标签怎么办?当您将这些差异可视化时,您将准确了解发生了什么。ReviewNB中的视觉差异显示了表格输出的变化。nbdev避免了很多合并冲突,因为它安装了githooks以首先删除导致冲突的元数据部分。如果在执行gitpull时遇到合并冲突,只需运行nbdev_fix_merge。运行此命令时,nbdev只需使用输出冲突的单元格的输出,如果单元格输入冲突,最终笔记本将包含两个带有冲突标记的单元格。这样你就可以很容易地找到它们并直接在Jupyter中修复它们。nbdev中基于单元格的合并冲突示例。nbdev只需创建标准Python模块即可创建模块化可重用代码。nbdev在代码单元格中查找特殊注释,例如#export(指示单元格应导出到Python模块)。每个笔记本都可以使用笔记本开头的特殊注释与特定的Python模块相关联。文档站点(使用Jekyll,因此它们直接由GitHubPages支持)是基于笔记本和特殊评论自动创建的。我们编写了自己的文档系统,因为Sphinx等现有方法无法提供我们需要的所有功能。至于代码导航,大多数编辑器和IDE(如vim、Emacs和vscode)都内置了一些不错的功能。GitHub的Web界面甚至直接支持代码导航(目前处于测试阶段,仅适用于某些选定的项目,例如fast.ai)。因此,我们确保nbdev导出的代码可以在任何系统中直接导航和编辑,并且任何编辑都会自动同步到笔记本中。至于测试,我们编写了自己的简单库和命令行工具。作为探索和开发(和文档)过程的一部分,可以直接在笔记本中编写测试,命令行工具在所有笔记本上并行运行测试。笔记本的自然状态是开发单元和集成测试的重要方式。您无需使用特殊语法来学习创建测试套件,只需使用Python中的常规集合和循环结构即可,因此要学习的新概念要少得多。这些测试也可以在常见的持续集成工具中运行,它们提供有关测试错误来源的明确信息。默认的nbdev模板集成了GitHubActions以实现持续集成等。动态Python在常规编辑器或IDE中完全支持Python的挑战之一是Python具有强大的动态特性。例如,您可以随时向类添加方法,使用元类系统更改类的创建方式和工作方式,并使用装饰器更改函数和方法的行为方式。Microsoft开发了语言服务器协议,可用于开发环境中以获取自动完成、代码导航等所需的当前文件和项目信息。然而,对于像Python这样真正动态的语言,这些信息通常只是猜测,因为提供正确的信息需要运行Python代码(Python不能这样做,原因有很多,比如编写可能处于混乱状态的代码,导致所有文件都被删除)。另一方面,notebook包含Python解释器的实际运行实例,它完全在您的控制之下。因此,Jupyter可以根据代码的实际状态提供自动完成、参数列表和上下文相关的文档。例如,当使用Pandas时,我们会为DataFrames的所有列名获取制表符补全。我们发现JupyterNotebooks的这一特性提高了探索性编程的生产力。它在nbdev中运行良好,无需任何更改。而这只是您通过构建基于JupyterNotebook的开发环境而免费获得的部分Jupyter功能。状态随着nbdev的发展,我们使用nbdev从头开始??编写fastaiv2。fastaiv2为构建深度学习模型提供了丰富且结构良好的API,将于2020年上半年发布。它现在功能齐全,早期采用者已经在使用预发布版本构建很酷的项目。我们还有其他用fastaiv2编写的项目,其中一些将在未来几周内发布。我们发现使用nbdev的效率是使用传统编程工具的1-2倍。这对我来说是一个巨大的惊喜。我已经编写代码30多年,并尝试了数十种用于构建程序的工具、库和系统,但我没有意识到在生产力方面还有如此大的改进空间。现在,我对未来感到兴奋,我认为开发人员生产力有很大的改进空间,我期待看到人们使用nbdev创建新项目。