\u200E
在飞桨开源社区能做什么?听听贡献者怎么说
发布日期:2022-10-13T07:57:14.000+0000 浏览量:836次

作为一个优秀的开源项目,飞桨汇聚了大批开发者与贡献者。像很多大型开源项目,飞桨框架一直很注重代码的规范性,并且随着代码仓库的演进持续加强。之前也有尝试过参与一些相关的优化,但如此庞大的一个Codebase并不是说随便改改就好的。所以,便有了本项「计划」:在开发工具链中增加规范检查和优化,让飞桨框架的代码更优雅,开发体验更好。

相比于一些新兴的语言来说,Python的工具链一直处于非常落后的状态,Python官方提供的工具链是非常不完善的,或者说是不「现代」的。因此可以看到Python的项目结构往往配置的形态各异,也使得参与开发的人需要对项目有一定熟悉程度才能上手。完整的工具链需要具备环境、依赖管理工具、Linter、Formatter、测试套件,这些都必不可少。本文重点介绍Linter和Formatter。
  • Formatter

Formatter 主要用于自动格式化代码,使得项目代码风格统一,避免项目协作时因为格式的调整而出现没必要的改动。合格的Formatter不会对代码语义进行任何更改,可以认为格式化是一个非常安全且可靠的操作,因此我们可以放心地在任何时刻运行它,而不必担心代码修改后出现问题。目前常见的格式化工具包括了yapf、black、autopep8等,还有一些只针对某些特定场景进行格式化的工具,比如isort,只格式化import语句的顺序。
  • Linter

Linter主要用于给开发者提示代码中存在的格式问题、语法问题和一些潜在的逻辑问题。与Formatter不一样的是,Linter往往是不进行自动修复的,最多只会给出一个修改建议,提示开发者手动对问题进行修复。由于Linter会给出一些潜在的逻辑问题,对于这些问题也只能手动修复。当然,Linter相比于Formatter所拥有最大的价值也在于提示这些逻辑问题。目前常见的Linter工具主要包含了Flake8、Pylint等,此外还有些静态类型检查工具,比如Mypy、Pytype、Pyright等。


将工具引入
工作流的各个阶段


在开发工作流中恰当的时机引入这些工具,可以很好提升代码的格式规范和质量。
一般情况下我们首先会在编辑器或者IDE上开发,然后通过git来进行一系列源代码的管理,之后推送到远程仓库,往往最后会触发相关的CI/CD来进行一些自动化测试、部署等操作。
我们在编辑器上进行开发时,通常会配置一些Linter或者Formatter工具,它们可以为我们及时地提供错误反馈及建议,使我们能够在开发过程中就解决大多数的格式等相关问题。当然,我们也可以手动在Terminal运行相应的CLI工具,来手动进行一些格式化等相关操作。
之后利用git进行commit、push等操作时,由于git在这些生命周期是可以注入一些hook的,因此我们也可以利用这个阶段来完成一些自动格式化、代码检查等操作。目前各个生态也都有比较成熟的hook管理工具,比如Python生态下的pre-commit,Node.js生态下的husky等等。利用这些工具可以轻松配置一些实用的hook,提高工作流的自动化程度。

最后就是CI,作为整个工作流的最后一步,CI往往有最完整的代码检查,这样即便开发者本地编辑器没有进行相关配置,或者本地没有安装pre-commit,在这一步都能够检查出来,保证合并到主线上的代码是没有相关问题的。


这些工具
如何实现


对于简单的代码格式修复,可以采用文本/正则搜索和替换。比如我之前为飞桨Paddle/docs项目做的自动在中英文间加空格(insert-whitespace-between-cn-and-en-char)就是使用正则实现的。但如果有更加复杂的需求,可能就需要手写一些复杂的规则进行处理了,一般情况下这种需求并不多,而且大多数文本都是有一定的语法规则的,这些拥有语法规则的文本直接在抽象语法树AST(Abstract Syntax Tree)上处理是更好的方式。这里拿之前我在进行单测报错信息优化时的一个简单的例子来说明一下AST的结构。

首先说明当时的一个需求,来源于GitHub上Paddle repo下的一个Issue:

Recommend to use np.testing.assert_allclose instead of assertTrue(np.allclose(...))  #44641
由于Paddle repo中的单测在进行比较时大多使用self.assertTrue(np.allclose(a, b))来断言两个np.ndarray在容忍误差范围内相等,而self.assertTrue只会区分True和False,这样的单测报错信息是不友好的,既不能知道a和b的值是多少,也不知道到底问题(diff)出现在哪里。另外,np.allclose对于shape也是不敏感的,np.allclose会在比较时自动应用广播机制,有些时候测试的检查并不是全面的。因此Issue中提出使用np.testing.assert_allclose(...)来替代self.assertEqual(np.allclose(...)),以提供更全面的错误信息。
乍一看可能会想到使用正则进行处理,不过稍微一想就会发现有很多坑,比如括号匹配的问题。但既然是处理符合Python语法的文本,我们直接交给最专业的Python语法分析工具肯定是最合适的啦。利用Python built-in的ast模块就可以将一段Python文本代码处理成一个AST结构,比如下面这段Python代码,在经过处理后即可得到一棵如下图所示的AST:
self.assertTrue(
   np.allclose(
      res[0],
      feed_add,
      rtol=1e-5),
   msg='blabla((((()()((xxxdfdf(')
可以从叶子结点上隐约看出原来的文本代码结构。
对AST进行处理会比直接对文本处理方便得多,比如我们可以通过遍历这个AST来找到我们需要的模式,然后对部分结点进行替换重构,形成新的AST。这个过程中使用的往往是访问者模式,在AST上处理过后,我们最后还是要将新的AST转换成文本代码,这样就完成了整个处理过程。
整体流程也如上图所示,是一个从文本到AST,经过若干处理后再转回文本的过程。

利用AST的工具不在少数,Formatter自不必说,Linter也有很大一部分是在AST上进行语法结构匹配和分析的。


个人贡献者
做什么


其实飞桨开源社区有非常多适合个人贡献者做的工作,比如目前我正在进行Flake8引入的计划。在这个过程计划同时引入isort等工具,但暂时不考虑black。因为目前飞桨框架已经使用yapf进行格式化,且几个月前刚刚重新进行了一次全量格式化,频繁更换格式化工具并不是一个明智的选择,在简单权衡了下利弊后我个人决定暂时不引入black,之后有时间再重新评估下收益。
此外,飞桨中文文档使用的是reStructureText编写,目前存在各种格式问题。其实对于这种文档来说rst并不是一种好的存储格式,我和另一位贡献者笠雨聆月也有一个重写文档生成流程的初步计划,敬请期待。
我在此前尝试给飞桨框架开发一个PEP 561中描述的stub only工具包,这对于飞桨框架目前缺失的类型提示非常有帮助,不过这个工具包目前仍在开发早期。希望未来飞桨框架内部能直接集成类型提示,这样不仅能够提供最好的类型提示效果,而且也能为飞桨开发者提供类型提示和验证效果,大大减少类型错误的发生。

欢迎大家加入我们,一起建设更优雅的飞桨框架~


加入我们


在这里,与我们一起定义飞桨框架的未来!

PFCC全称Paddle Framework Contributor Club,意为飞桨框架贡献者俱乐部,是一个有兴趣、正在或者已经为飞桨开源框架做开源贡献的贡献者成立的虚拟组织。在这里,飞桨开源框架的贡献者进行讨论、交流和分享,并为飞桨框架做出持续的贡献。
如果你有意愿加入我们,欢迎描二维码进群,与我们一起参与到飞桨框架的开发中吧!

本文作者

Nyakku Shigure,飞桨开源社区活跃贡献者,PFCC成员,飞桨Paddle与docs项目Committer。

相关链接

  • 作者博客

https://nyakku.moe/posts/2022/09/21/moefy-paddle-dx-improvements.html

  • Call for Contributions

https://github.com/PaddlePaddle/community/tree/master/pfcc/call-for-contributions

  • PFCC 2022-09-08会议纪要

https://github.com/PaddlePaddle/community/blob/master/pfcc/2022-09-08-meeting-minutes.md

推荐阅读

在这里,与你一起定义飞桨框架的未来


关注【飞桨PaddlePaddle】公众号

获取更多技术内容~