fastai 编码风格
这是对 fastai 编码风格的简要讨论,该风格在一定程度上受到了过去 60 年 APL / J / K 编程社区发展的思想(一个稀释版本)的影响,同时也结合了 Jeremy 在过去 25 年参与编程语言设计和库开发的个人经验。该风格特别设计为与科学编程和迭代、实验性开发的需求相一致。
每个人对编码风格都有强烈的看法,除了一些非常有经验的程序员,他们使用过多种语言,意识到有许多不同的完全可接受的方法。整体而言,Python 社区对此有特别强烈的观点。我怀疑这与 Python 是一门面向初学者的语言有关,因此有许多用户在其他语言方面经验有限;不过这只是猜测。无论如何,我并不太在意你在为 fastai 贡献代码时使用哪种编码风格,只要:
- 你不改变现有代码以减少其符合本风格指南的程度(特别是:不要使用自动的代码检查器/格式化工具!)
- 你尽量使你的代码与周围的代码不至于差异过大。
话虽如此,我确实希望你能在本风格指南中找到一些引发思考的想法,并在为这个库贡献代码时考虑采用它们。
我个人对编码风格的看法深受 Iverson 在 1979 年图灵奖(计算机科学的“诺贝尔奖”)演讲 Notation as a Tool For Thought 的影响。如果你有时间,这篇论文非常值得一读并仔细消化(它是计算机科学史上最重要的论文之一),代表了自 1964 年 APL 发布以来一个思想的演变。Iverson 说:
本文的论点是,编程语言中发现的执行性和普遍性的优势可以有效地结合,在一个单一的连贯语言中,与数学符号所提供的优势相结合。
论文中的一个关键思想是“简洁有助于推理”,这一思想被纳入各种指南中,如“缩短沟通线路”。这有时被错误地认为仅仅意味着“简洁”,但它是一个更深层次的想法,正如在 这个 Hacker News 帖子中所描述的那样。我无法在这里总结这种思考,但我可以指出几个关键好处:
- 它支持 说明性编程,特别是当与 Jupyter Notebook 或类似为实验设计的工具结合使用时
- 我所知的世界上最有效率的程序员,如非凡的 Arthur Whitney 经常使用这种编码风格(这可能是一个巧合!)
风格指南
Python 随着时间的推移,吸收了许多使其更适合这种编程形式的想法,例如:
- 列表、字典、生成器和集合推导式
- Lambda 函数
- Python 3.6 插值格式字符串
- Numpy 基于数组的编程
尽管 Python 总是比许多语言更冗长,但通过大量使用这些特性,以及一些简单的经验法则,我们可以努力将一个语义概念的所有关键思想保持在代码的一个屏幕内。这是我在编程时的主要目标之一——我发现如果我必须四处跳转才能将各个部分组合在一起,就很难理解一个概念。(或者如 Arthur Whitney 所说“我讨厌滚动”!)
符号命名
- 遵循标准的 Python 大小写指南(类名使用 CamelCase,其他大部分使用 under_score)。
- 一般来说,目标是要像 Perl 设计者 Larry Wall 所描述的那样,比喻为 霍夫曼编码:
以比喻的方式向霍夫曼的压缩代码致敬,该代码为更常见的字节分配了较少的比特数。在语法上,这意味着常用的事物应该更短,但你不应将短序列浪费在不太常见的构造上。
- 一个相当完整的缩写列表在 abbr.qmd 中;如果你发现有任何遗漏,欢迎编辑此文件。
- 例如,在计算机视觉代码中,我们经常提到“size”和“image”,我们使用缩写形式
sz
和img
。或者在NLP代码中,我们会用lm
代替“language model”。 - 在列表推导式中使用
o
表示对象,i
表示索引,在字典推导式中使用k
和v
表示键和值。 - 使用
x
表示算法(如层、变换等)的输入张量,除非在与某个库交互时,这种行为不符合预期(例如,如果编写一个pytorch损失函数,使用input
和target
作为该库的标准)。 - 查看你正在编写的代码部分的命名约定,并尽量遵循它们。例如,在
fastai.transforms
中,你会看到’det’表示’deterministic’,‘tfm’表示’transform’,’coord’表示坐标。 - 假设编码者对你所工作的领域有所了解
- 例如,使用
kl_divergence
而不是kullback_leibler_divergence
;或者(像pytorch一样)使用nll
而不是negative_log_likelihood
。如果编码者不知道这些术语,他们仍然需要在文档中查找并学习这些概念;如果他们知道这些术语,缩写将会被很好地理解。 - 在实现论文时,尽量遵循论文的命名法,除非它与其他广泛使用的约定不一致。例如,使用
conv1
而不是first_convolutional_layer
。
- 例如,使用
虽然很难设计一个真正引人注目的实验来证明这一点,但有一些有趣的研究支持了过长符号名称会负面影响代码理解的观点。
布局
代码的宽度应小于标准现代小屏幕(当前为1600x1200)在14pt字体大小下填充的字符数。这意味着大约160个字符。遵循这一规则意味着很少有人需要横向滚动来查看你的代码。(如果他们使用限制单元格宽度的jupyter notebook主题,那是他们需要解决的问题!)
一行代码应尽可能实现一个完整的想法
因此,
if
部分及其单行语句应放在同一行,使用:
分隔使用三元运算符
x = y if a else b
可以帮助遵循这一指南如果单行函数体舒适地适合与
def
部分在同一行,可以自由地将它们放在一起,使用:
如果你有一堆做类似事情的单行函数,它们之间不需要空行
def det_lighting(b, c): return lambda x: lighting(x, b, c) def det_rotate(deg): return lambda x: rotate_cv(x, deg) def det_zoom(zoom): return lambda x: zoom_cv(x, zoom)
尽量对齐概念上相似的语句部分。这允许读者快速看到它们的不同之处。例如,在这段代码中,可以立即清楚地看到两部分调用相同的代码,但参数顺序不同。
if self.store.stretch_dir==0: x = stretch_cv(x, self.store.stretch, 0) else: x = stretch_cv(x, 0, self.store.stretch)
使用解构赋值将所有类成员初始化放在一起。这样做时,在逗号后不留空格,但在等号周围留空格,以便清楚地看到LHS和RHS。
self.sz,self.denorm,self.norm,self.sz_y = sz,denorm,normalizer,sz_y
尽量避免使用垂直空间,因为垂直空间意味着你不能一目了然地看到所有内容。例如,更喜欢在一行中导入多个模块。
import PIL, os, numpy as np, math, collections, threading
使用4个空格进行缩进。(事后看来,我希望我选择了2个空格,像Google的风格指南一样,但我不想回去改变一切…)
在添加运算符周围的空格时,尽量遵循符号约定,使你的代码看起来类似于特定领域的符号。例如,如果使用pathlib,不要在
/
周围添加空格,因为这不是我们在shell中写路径的方式。在方程中,使用空格来布局方程的不同部分,使其尽可能接近常规数学布局。避免尾随空格
算法
- fastai旨在展示可能的最佳效果。因此,尽量确保你实现的算法至少与其他存在的版本一样快、准确和简洁(如果它们存在),并使用分析器检查热点并适当优化(如果代码在实践中运行时间超过一秒)。
- 尽量确保你的算法扩展性良好;具体来说,它应该在16GB RAM上处理任意大的数据集。这通常意味着使用惰性数据结构,如生成器,而不是将所有内容拉入列表。
- 在代码的适当部分添加注释,提供你正在实现的论文中的方程编号。
- 尽可能使用numpy/pytorch的广播功能,而不是循环。
- 尽可能使用numpy/pytorch的高级索引,而不是专门的索引方法。
- 在实际尝试使用最新热门论文的方法并在几个数据集上进行比较,确认其在实践中确实有用之前,不要提交实现该论文的PR!理想情况下,在PR中包含一个gist链接的notebook,展示这些结果。
其他事项
- 可以自由假设已安装最新版本的python和关键库。但如果依赖于仅几个月前发布的内容(包括最近修复的bug),请在PR和文档中提及。但不要依赖任何未发布或测试版。
- 除非有必要告诉读者你为什么这样做,否则避免使用注释。要告诉他们你是如何做的,使用符号名称和清晰的说明性代码。
- 如果你在实现一篇论文或遵循其他外部文档,请在代码中包含一个链接。
- 如果你几乎使用了某个模块提供的所有内容,只需
import *
。没有必要单独列出所有导入的内容!为了避免导出实际上仅供内部使用的内容,定义__all__
。(目前遵循__all__
指南,欢迎PR来修复此问题。) - 假设用户使用现代编辑器或IDE,并且知道如何使用它们。例如,如果他们想浏览方法和类,他们可以使用代码折叠——他们不需要依赖于类之间有两行空行。如果他们想查看符号的定义,他们可以跳转到引用/标签,然后不需要在文件顶部列出导入列表。等等…
- 不要使用像autopep8这样的自动linter或像yapf这样的格式化工具。没有自动工具能像你一样细心和理解领域地布局你的代码。而且它会破坏之前贡献者在该文件中使用的所有细心和领域理解!
- 保持你的PR小,对于任何有争议或棘手的内容,请先在论坛上讨论。
文档
- 文档主要放在
docs_src
中的notebook中,用于创建HTML文档 - 在代码中,添加一行包含反引号引用主要参数名称的docstring
- Python re模块是我们所寻找的文档风格的良好范例。
常见问题
- 为什么不使用PEP 8?
- 我认为它不适合我们使用的编程风格,也不适合数学密集型代码。如果你从未使用过PEP 8以外的任何东西,这是一个尝试和学习新东西的机会!
- 我的编辑器在fastai中抱怨PEP 8违规;我该怎么办?
- 几乎所有编辑器都有能力为项目禁用linting;找出如何在你的编辑器中做到这一点。
- 你是否担心使用不同的风格指南可能会吓跑新的贡献者?
- 并不真的担心。我们对风格并不那么挑剔,所以我们不会拒绝不符合本文档格式的PR。而且,虽然周围有一些人非常固执己见,无法接受新事物,但他们肯定不是我们想要合作的那种人!