fastai 编程风格
这是对 fastai 编码风格的简要讨论,它松散地借鉴了过去 60 年来在 APL / J / K 编程社区中发展起来的思想(一个大大淡化了的版本),以及 Jeremy 在过去 25 年中对编程语言设计和库开发的个人经验。这种风格特别设计用于适应科学编程和迭代、实验性开发的需求。
每个人对编码风格都有强烈的看法,除了也许一些非常有经验的程序员,他们使用过许多语言,并意识到存在许多完全可接受的不同方法。总的来说,Python 社区在这方面有特别强烈的观点。我怀疑这与 Python 是一种面向初学者的语言有关,因此有很多用户在其他语言方面的经验有限;但这只是猜测。无论如何,我并不太在意你在为 fastai 贡献时使用哪种编码风格,只要:
- 你不要修改现有代码以降低其符合本风格指南的要求(特别是:不要使用自动 linter / 格式化工具!)
- 你要做出一些基本的尝试,使你的代码与周围的代码没有太大差异。
话虽如此,我确实希望你会发现本风格指南中的想法至少有一点启发性,并在为本库贡献时考虑在一定程度上采纳它们。
我个人对编码风格的方法深受 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 比喻描述的霍夫曼编码(Huffman Coding)
“为了象征性地纪念霍夫曼的压缩码,它为更常见的字节分配了更少的位数。在语法方面,它简单地意味着常用的东西应该更短,但不应该在不那么常见的结构上浪费短序列。”
- 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’ 代表 coordinate。 - 假设编码人员了解你正在工作的领域知识。
- 例如,使用
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 个空格,就像谷歌的风格指南一样,但我不想回头修改所有内容……)
在操作符周围添加空格时,尝试遵循符号约定,使你的代码看起来类似于领域特定的符号。例如,如果使用 pathlib,不要在
/
周围添加空格,因为我们在 shell 中不是这样写路径的。在等式中,使用空格布局等式的各个部分,使其尽可能类似于常规数学布局。避免行尾空格。
算法
- fastai 旨在展示可能实现的最佳结果。因此,请确保你的算法实现至少与现有版本(如果存在)一样快速、准确和简洁,并使用性能分析器检查热点并适当地优化它们(如果代码在实践中运行超过一秒)。
- 尝试确保你的算法具有良好的伸缩性;具体来说,它应该能在 16GB 内存上处理任意大的数据集。这通常意味着使用惰性数据结构,例如生成器,而不是将所有内容都加载到列表中。
- 在代码的适当部分添加注释,提供你正在实现的论文中的方程式编号。
- 尽可能使用 numpy/pytorch 的广播功能,而不是循环。
- 尽可能使用 numpy/pytorch 的高级索引,而不是专门的索引方法。
- 在实际尝试在几个数据集上使用它、将其与现有方法进行比较并确认它在实践中确实有用之前,不要提交实现最新热门论文的 PR!理想情况下,在你的 PR 中包含一个笔记本作为 gist 链接,显示这些结果。
其他
- 请随意假设已安装了最新版本的 python 和关键库。但如果你依赖于几个月前才发布的东西(包括最近修复的 bug),请务必在 PR 和文档中提及。但是,不要依赖任何未发布或 beta 版本。
- 避免添加注释,除非有必要告诉读者你做某事的原因(why)。要告诉他们你如何做(how),请使用有意义的符号名称和清晰的阐释性代码。
- 如果你正在实现一篇论文或遵循其他外部文档,请在你的代码中包含该文档的链接。
- 如果你几乎使用了模块提供的所有内容,只需
import *
。无需单独列出你导入的所有内容!为了避免导出那些实际用于内部使用的东西,请定义__all__
。(注意:我们目前正在遵循__all__
指南,欢迎提交 PR 修复此问题。) - 假设用户拥有现代编辑器或 IDE,并知道如何使用它。例如,如果他们想浏览方法和类,他们可以使用代码折叠——他们不需要依赖于在类之间留两行空行。如果他们想查看符号的定义,他们可以跳转到引用/标签,而不需要在文件顶部列出导入项。以此类推……
- 不要使用自动 linter,例如 autopep8,或格式化工具,例如 yapf。没有任何自动化工具可以像你一样,带着细心和领域理解来排版你的代码。而且它会破坏以前贡献者在该文件中使用的所有细心和领域理解!
- 保持你的 PR 较小,对于任何有争议或棘手的问题,先在论坛上讨论。
文档
- 文档主要放在
docs_src
中的 notebook 文件中,这些文件用于生成 HTML 文档。 - 在代码中,添加一个单行 docstring,其中包括对主要参数名称的反引号引用。
- Python re 模块是我们在文档风格方面寻找的一个好榜样。
常见问题解答
- 为什么不使用 PEP 8?
- 我认为它不适合我们使用的编程风格,也不适合数学密集型代码。如果你除了 PEP 8 之外什么都没用过,这是一个尝试和学习新东西的机会!
- 我的编辑器报告 fastai 中存在 PEP 8 违规;我该怎么办?
- 几乎所有编辑器都有禁用项目的 linting 功能;请找出如何在你的编辑器中执行此操作。
- 你担心使用不同的风格指南会阻碍新的贡献者吗?
- 并不是。我们对风格并不是那么挑剔,所以我们不会因为不符合本文件格式的 PR 而拒绝。虽然有一些思维狭隘、无法接受新事物的人,但他们肯定不是我们希望合作的人!