这篇blog整理下写python程序时应注意的问题。在写程序时,我们的代码的整洁和规范会直接影响到该代码的表达能力,从机器的指令清晰高效到人的理解消化。没有个性的代码没有活力,但没有规范的代码没有生命力。Python作为已经很通用的一门高级语言,这方面的语言层次的考虑已经有不少,但同时还是需要人们在使用时自我规范一下,以让代码更加地整洁。
参考一些比较权威的文档,本文从代码的通用规范,Python的语言准则,Python的风格进行评述,学习并简单分析摘要一下,聊以自用。
通用规范
- [MUST] 最小化代码的作用范围
- [MUST] 语义的明晰大于任何技巧和优化
- [MUST] 一致性大于个性
- [MUST] 尽可能地将代码纳入控制
- [SHOULD] 尊重传统和习惯
- [SHOULD] 拒绝语法糖
语言准则
- [MUST] 仅对包和模块使用导入
Why: 没什么好说的,模块和包之间仅仅知道彼此的名字即可,不应导入更加细节的实现
- [MUST] 使用模块的全路径名来导入每个模块
Why: 虽然会增加调整模块布局的难度,但这个做法规范而且必要,可以避免模块的混乱,参见C++/C的namespace和Java的package设计,这里利用全路径名导入有其必须之处
- [SHOULD] 允许使用异常, 但必须小心
Why: Google一直避免异常处理机制!老实地说,异常处理是我在语言学习过程中消化不良的一块,初衷很好,但却牺牲了程序的可读性和性能。应用不当的话,异常处理更是成为bug的温床。但既然Python是引入异常机制的,这里还是应该允许使用,但需要强加限制。
- [MUST] 模块要么使用异常,要么杜绝
Why: 一致性的要求,这样可保证模块在被使用时的语义简明。模块或包应该定义自己的特定域的异常基类, 这个基类应该从内建的Exception 类继承。模块的异常基类应该叫做”Error”.
- [SHOULD] 最小化异常处理
Why: 最小化异常处理指的是try和except, finally的内容都尽可能地少,raise和except尽可能地精确地使用对应异常,避免扩大范围带来的种种灾难。
- [MUST] 不要把异常抛出程序
Why: 把异常扔给系统处理是糟糕的,我们完全可以用except把对应的异常捕获并用trace_back打印出来。当异常不纳入控制之时,就是抛弃异常之时。
- [SHOULD] 避免全局变量
Why: 全局变量可以有,仅仅是当你的程序还不够一个模块的规模时。这条规则也可定义为“最小化全局变量使用”。当可以用类成员变量,局部变量等替代时,就应避免全局变量的使用。如果你不得不使用全局变量时,请保证只读。
- [SHOULD] 鼓励嵌套/局部/内部类或函数
Why: 局部类不能被序列化,所以在不需要序列化时,可利用嵌套/局部/内部来约束该类或函数的作用域。
- [SHOULD] 鼓励列表推导
Why: python的列表推导语法还算简明清晰,用类英语的语法方式来表达,个人认为可以鼓励使用。列表推导的前提是该语法足够清晰,而不易引起误会。这里不鼓励花活和过度嵌套。
- [MUST] 使用默认迭代器和操作符
Why: 当某类有默认迭代器和操作符,我想不出有什么理由不去使用。
- [SHOULD] 迭代时可使用生成器
Why: 看到的相关文章基本上是把Generator和Iterator结合在一起,这也是Python提供这一特性的初衷。想着应该是有巧用Generator的地方和技巧,但这里还是建议仅在迭代时使用生成器,可以有效节省内存。
- [SHOULD] 单行使用Lambda函数
Why: Lambda是语法糖,so,适合点缀一下。单行使用Lambda函数来换取简便,当更加繁复时,还是要老老实实地使用函数
- [SHOULD] 约定使用默认参数
Why: 默认参数可方便函数的使用,但默认参数必须是不可变变量,同时调用时需要改写该变量时应用arg=default的方式来表明该默认参数的改变
- [SHOULD] 避免使用Properties
Why: ...
- [MUST] 逻辑判断用内置false
Why: Python里的True和False代表布尔值,但在进行逻辑判断时, None, "", [], {}, 0的内存表示均为0(NIL),这个时候应直接进行逻辑判断,而不是额外地进行比值逻辑判断
- [SHOULD] 避免过时的语言特性
Why: Python很活跃,也很不稳定。在语言设计上并不是很良好,故而在历史中出现一些已被废弃的方法,如string, map等,这些都是饱受批评的方法和特性。
- [SHOULD] 避免静态Scoping
Why: Google规范说鼓励,可是考虑到静态Scoping和传统C/C++的语义不一致,这里应考虑避免使用。
- [MUST] 避免使用函数与方法装饰器
Why: 虽然规范和很多包都惯用装饰器,但我想不出用它的理由,又隐蔽又可替代。如果只是为了减少代码,这样的做法不够明智。
- [SHOULD] 避免多线程python
Why: Python语言在设计之初即对多线程的支持不足,它的考虑是作为一种通用脚本语言。多线程程序的设计过于复杂,而python的原建类型都不能保证原子操作。在编写多线程python程序时,我们需要保证该程序的设计清晰和简单。否则,还是换门更合适的语言吧。
- [SHOULD] 避免炫的特性
Why: python如蟒蛇,吞食了很多好玩和时尚的特性,如元编程,反射等。但在编程中,这些炫酷特性的使用带来的麻烦往往多过好处。降龙,十八掌足矣。
- [SHOULD] 类归于继承树
Why: python虽然兼容并包,但本质上是“万物皆类”的思想。在编写我们自己的类时,最好是符合该思想,要么继承自定义的基类,要么继承自Object这个原生基类。
编码风格
- [SHOULD] 句尾没有分号
Why: 句尾分号是很多人憎恨C/C++的原因之一,我也不例外。
- [MUST] 每行不超过80 个字符
Why: 这是行规。
- [SHOULD] 宁缺毋滥的使用括号
Why: 这是很有意思的反C/C++特性的地方,括号会影响对代码的理解和阅读,在python和Go等语言中避免括号的过多使用,这一点符合最简实践。
- [MUST] 用4个空格来缩进代码
Why: 代码应该文如其见,tab键非常隐蔽和平台不一致,没有存在的理由。对于Go包容tab,我很失望
- [SHOULD] 顶级定义之间空两行, 方法定义之间空一行
Why: python没有大括号,在代码实现中空行是一个不大好的习惯,至于定义直接空几行,我觉得无所谓,明晰一致就好。
- [SHOULD] 英语语法的空格使用
Why: python的追求是向自然语言靠拢,我们在标点之后保持空格即可,这一点比C好
- [MUST] 每个模块都应该以#!/usr/bin/env python<version> 开头
Why: python的语言升级是糟糕的,版本之间跨度和不一致性蛮大。你不这么写,代码会被语言搞死。当然了,通常也不会有这么夸张,但请专业点!
- [SHOULD] 注释应可文档化
Why: 注释是代码的一部分,是用来解释代码的最好工具。python甚至有一个__doc__的内置方法。如果我们再用Sphinx等第三方文档化工具的要求去编写注释,那你的代码不会太难看。
- [SHOULD] Google命名规范
Why: Google命名规范较为合理和简单,而且我已惯用。在C/C++,Go和python中,尽量保持语言见命名的一致性,这样会降低很多语言习惯的迁移工作。基本上python的特有命名方式是_var(私有变量)和__func(私有方法),这点区别于Go。
- [MUST] if __name__ == ’__main__’前导的main入口
Why: python是脚本语言,如果不这样写,在模块被调用时很尴尬。这个可作为单元测试的很合适的一个入口进行维护,你说呢?
- [MUST] 代码文件使用UTF-8编码
Why: 我们需要统一的字符编码,任何编码转换应该显式进行,UTF-8编码最为普适,就是它了。在每个源文件的头前我们应该加上: # -*- coding: utf-8 -*-
在人工遵守规范之余,我们可以利用pychecker这样的工具来检查代码,也可以利用Doxygen, Sphinx这样的工具来生成文档。后续有时间会研习一些这些攻城利器。
在代码编写过程中,我们应以程式化地方法去对待我们敲出来的东东。因为程式,所以通用。这是编程规范存在的理由,也是提高我们产出的有效方法。
参考:
[1] Google Python 风格指南
[2] Python: Python 编码风格指南中译版(Google SOC)
[3] PEP8 Python Coding Rule
Written by Steve Lee(llwgod@gmail.com), 2013-08-08