PythonPEP8编码规范
Python 编码规范
(一)命名规范
-
变量、参数、方法命名采用
lower_snake_case
(小蛇式)命名方法。示例:
反例: Name、NAME、SEND_MESSAGE、 Send_Message、SendMessage、SENDMESSAGE 正例: name、update_phone
-
类命名采用
UpperCamelCase
(大驼峰式)命名方法。示例:
反例: accountService 正例: AccountService
-
常量命名采用
UPPER_SNAKE_CASE
(大蛇式)命名方法。说明: 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
示例:
反例: max_stock_count 正例: MAX_STOCK_COUNT
-
避免使用单字母变量,尽量细化变量含义,命名要有意义,做到见名知义。
说明: 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。
示例:
给姓名、性别赋值 反例: a, b = "张三", "男" 正例: name, sex = "张三", "男"
-
禁止出现单词拼写错误的情况。
示例:
校验单词 反例: vilidate 正例: validate
-
对于接收多个返回值的情况,接收返回值时只接收要用的,其它的可以用
_
忽略。示例:
程序中只用到了姓名,其他没用到 反例: name, sex, age = fun() 正例: name, _, _ = fun()
-
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用。
示例:
给变量 ‘电话’ 赋值 反例: dianhua = "8722334" #(中文拼音) 正例: telephone = "8722334"
-
使用缩写进行命名时,要采用公认的、约定俗成的缩写,切不可自造缩写。
说明: 杜绝完全不规范的缩写,避免望文不知义。
示例:
给变量 ‘电话’ 赋值 反例: dh = "8722334" #(电话两字的首字母) tp = 8722334 #(telephone 拆解) 正例: tel = "8722334"
-
避免在名称中包含易混淆的字符。
说明:
示例:
1(数字 1)和 l(小写字母L) 1和I(大写字母i) 0(零)和O(大写字母o) 2(数字2)和Z
-
采用名词或者形容词来命名变量, 对于集合变量,使用类型或者名次复数s作为后缀。
说明: 变量一般情况下建议使用名词、名字组合或者形容词,因为变量一般形容的是一种事物或者事物的属性,所以用名词或者名词组合更容易让人理解,而形容词一般用于 Bool 类型的变量。
示例:
apple = "苹果" apples = ["苹果1", "苹果2"]
-
在一个代码块中,变量作用域越大,命名应该越长越精确。相应的,变量名越短,就应该保持变量的作用域越小。
-
在相关的代码中保持全局命名风格的一致,变量命名前后用词需统一。
说明: 在同一项目中,如果在某一模块中使用
state
作为状态的变量名,那么尽可能的在其他模块中也使用state
作为状态的变量名,切不可即存在state
, 又存在status
等等示例:
反例: a.py state = 100 b.py status = 100 c.py stat = 100 正例: a.py b.py c.py state = 100
-
变量、参数命名时不建议使用内置函数名称。
说明:
示例:
反例: len = 123 id = "123" map = "222" dict = {}
-
命名规约。
说明:
方法命名: 1)
get/get_
: 获取单个对象; 2)find_one
: 获取单个对象; 3)find/find_
: 获取多个对象,复数结尾; 4)count_
: 统计; 5)batch_
: 批量; 6)create_
: 插入/创建; 7)mark_done
: 标记为已完成状态; 8)mark_doing
: 标记为进行中状态; 9)mark_closed
: 标记为已关闭状态; 10)mark_enabled
: 标记为已启用状态; 11)mark_disabled
: 标记为已禁用状态; 12)mark_deleted
: 标记为已删除状态; 13) … -
【推荐】下划线、双下划线的使用。
说明:
前置单下划线:
_var
用来表示该名称仅在内部使用。一般对Python
解释器没有特殊含义(通配符导入除外),只能作为对程序员的提示。后置单下划线:
var_
命名约定,用于避免与Python
关键字发生命名冲突。前置双下划线:
__var
在类环境中使用时会触发名称改写,对Python
解释器有特殊含义。前后双下划线:
__var__
表示由Python
语言定义的特殊方法。在自定义的属性中要避免使用这种命名方式。单下划线:
_
单下划线有时用作名称,来表示变量是临时的或无关紧要的。
(二)常量/枚举值规范
-
代码中所有的枚举值必须在常量中定义。
说明: 枚举值在常量中定义,能做到统一管理,并且能通过常量名称知晓枚举的含义。
示例:
反例: if sex == 1: return False 正例:[能通过常量的名称知晓所指的含义] if sex == constants.MALE: return False
-
不要使用一个常量类或者常量文件维护所有常量,按常量功能进行归类,分开维护。
说明: 大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。
-
在定义枚举值时要预留好扩展空间。
说明: 预留扩展空间方便日后需求增加对整体结构不造成影响。
示例:
以外卖配送单据为例,如果第一版需求配送单状态定义为: ORDER_STATE_RECEIVED = 1 # 配送员已接单 ORDER_STATE_PICKED = 2 # 配送员已取货 ORDER_STATE_DONE = 3 # 配送员配送完成 第二版要在接单与取货之间增加到达取货点的状态,在上面的枚举定义上就不好扩展,建议在定义枚举值的时候做好预留空间,比如: ORDER_STATE_RECEIVED = 10 # 配送员已接单 ORDER_STATE_PICKED = 20 # 配送员已取货 ORDER_STATE_DONE = 100 # 配送员配送完成 如果增加到达配送点的状态,则只需这样变动: ORDER_STATE_RECEIVED = 10 # 配送员已接单 ORDER_STATE_ARRIVED = 15 # 配送员到达取货点 ORDER_STATE_PICKED = 20 # 配送员已取货 ORDER_STATE_DONE = 100 # 配送员配送完成
-
枚举值不推荐使用
0
、None
、False
等字段。说明: 在做逻辑判断时,需要更多的代码去验证是否满足需求。
示例:
SEX_MAN = 0 account_sex = False if account_sex == SEX_MAN: print True else: print False 结果为True
(三)代码格式
-
不允许无脑式的复制、粘贴代码。
说明: 无脑式的复制代码,会把一些用不到的东西也会复制过来,对后面维护的人简直是灾难。
-
任何二目、三目运算符的左右两边都需要加一个空格。
说明: 运算符包括赋值运算符
=
、逻辑运算符、加、减、乘、除符号等。示例:
反例: c=a+b 正例: c = a + b
-
采用 4 个空格缩进,禁止使用
Tab
字符。说明: 如果使用
Tab
缩进,必须设置 1 个Tab
为 4 个空格。 -
每一个逻辑代码块结束时要增加一个空行。
说明: 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
示例:
反例: if a < 10: pass elif a == 10: pass else: pass 正例: if a < 10: pass elif a == 10: pass else: pass
-
每一个
py
文件结束时,要增加一个空行,不允许多个空行。说明:
-
参数、包、模块未被使用时要清理掉。
说明: 只引入在代码中使用的
示例:
反例: import time from bson import ObjectId import logging print time.time() 正例: import time print time.time()
-
单行字符数限制不超过 120 个,超出需要换行。
说明:
PEP8
要求每行代码不超过80个字符;实际上是79个。 缘由是:我们已经不是使用VT100
终端编程的年代了,我们有了更大,更高分辨率的屏幕。 目前我们要求单行代码长度不允许超过120个字符。 -
方法参数在定义和传入时,多个参数逗号后边必须加空格。
说明:
示例:
反例: fun(a,b,c) 正例: fun(a, b, c)
-
每个
py
文件在头行必须添加 # -- coding:utf-8 --。说明:
-
在 Python2 版本中文字符前必须加
u
。说明:
示例:
反例: name = "张三" 正例: name = u"张三"
-
自然语言使用双引号,机器标示使用单引号,正则表达式使用原生的双引号,文档字符串
(docstring)
使用三个双引号。说明:
示例:
正例: name = u"张三" data['name'] = name phone_regex = r"^1\d{10}$"
-
模块级函数和类定义之间空两行;类成员函数之间空一行;函数中可以使用空行分隔出逻辑相关的代码。
说明:
示例:
class A(object): def __init__(self): pass def main(self): pass
(四)控制语句
- 表达异常的分支时,少用
elif
方式,及早失败,避免嵌套
*说明:*
*示例:*
```
反例1:
if a < 10:
pass
elif a == 10:
pass
else:
pass
return
正例1:
if a < 10:
pass
return
if a == 10:
pass
return
return
反例2:
if a < 1:
b = 1
else:
b = 2
正例2:
b = 2
if a < 1:
b = 1
反例3:
if a < 1:
...编写了大概50行代码
else:
return False
正例3:
if a >= 1:
return False
...编写大概50行代码
及早失败,避免嵌套
```
*延伸:* 最少编码原则
最少编码指的是代码不仅仅全面解决了问题,而且除了执行能够准确地解决问题的逻辑行,没有其他多余的行。代码要尽可能普通,简单的代码也是优雅的代码,程序员看到这样的代码会感到愉悦。
最少编码和设计良好的解决方案之间有着密切的关系,优秀的解决方案可以大大减少代码量。
```
例如:校验验证码是否正确
- 如果系统当前配置的是模拟发送,则要判断填写的验证码是否与系统配置的是否一致,一致则通过;
- 如果传入的手机号是系统白名单配置的,则只需校验验证码与系统配置的一致则通过;
- 如果系统当前配置的是真实发送验证码,则要判断当前传入的验证码是否与数据库存在的是否一致,一致则通过;
重构前:
phone = "18888888888" # 手机号
verify_code = "123456" # 验证码
trust_phones = ["18888888888"] # 系统设置的手机号白名单
is_sms_send = True # 是否真实发送验证码
default_code = "999999" # 系统设置的默认验证码
correct_verify_code = "123213" # 正确的验证码
def check_verify_code(phone, verify_code, trust_phones, is_sms_send, default_code):
"""
校验验证码是否正确
:param phone:
:param verify_code:
:param trust_phones:
:param is_sms_send:
:param default_code:
:return:
"""
# 模拟发送
if not is_sms_send:
# 判断传入的验证码如果不等于默认的验证码则返回False
if verify_code != default_code:
return False
else:
return True
# 真实发送
else:
# 手机号在信任的手机号里面
if phone in trust_phones:
# 判断传入的是否等于默认的
if verify_code != default_code:
return False
else:
# FIXME 通过手机号找到与之对应的验证码
correct_verify_code = ""
if correct_verify_code != verify_code:
return False
return True
重构后:
def check_verify_code2(phone, verify_code, trust_phones, is_sms_send, default_code):
"""
校验验证码是否正确
:param phone:
:param verify_code:
:param trust_phones:
:param is_sms_send:
:param default_code:
:return:
"""
# 手机号在信任的手机号里面
if phone in trust_phones:
# 传入的等于默认的
if verify_code == default_code:
return True
return False
# 真实发送
if is_sms_send:
# FIXME 通过手机号找到与之对应的验证码
correct_verify_code = ""
if correct_verify_code == verify_code:
return True
return False
# 判断传入的验证码如果不等于默认的验证码则返回False
if verify_code == default_code:
return True
return False
通过对比发现:
- 重构后的代码减少了 `if else` 的嵌套,可读性提高,代码更简洁,逻辑清晰;
- 考虑到真实发送的场景要远大于模拟发送的场景,所以把判断顺序进行了调整,提高了效率。
```
-
不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
说明: 很多
if
语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢?示例:
当满足a < 0 且 b > 3 且 c == 4 时,为锁定状态,执行对应的逻辑 反例: if a < 0 and b > 3 and c == 4: pass 正例: is_lock = a < 0 and b > 3 and c == 4 if is_lock: pass
-
循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的
try-except
操作(这个try-except
是否可以移至循环体外)。说明:
示例:
反例: ids = [] for _id in ids: try: record = get_record(_id) except Exception as e: pass record['b'] = "" pass 正例: ids = [] try: for _id in ids: record = get_record(_id) record['b'] = "" pass except Exception as e: pass
-
函数中的所有
return
语句应该返回一个表达式,或者都不返回。 (不允许部分返回,不允许返回不同的类型)。说明: 如果函数没有指定返回值,则返回
None
,返回空值是Python
的核心功能,但是使用显式的return None
语句能更清楚地表达代码的意图。示例:
反例: def test(a, b): if a > b: return True if a < b: return 0 else: pass 正例: def test(a, b): if a > b: return True if a < b: return False return False
-
判断某字符是否在某范围内时,建议范围数据使用
set
而不是list
。说明:
示例:
正例: ids = set(record['_id'] for record in records) if a in ids: pass 反例: ids = list(record['_id'] for record in records) if a in ids: pass
###(五)注释
- 类、类属性、类方法、方法必须要有注释,使用
"""内容"""
格式,不要使用# 内容
。
*说明:* 注释除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。
*示例:*
```
```
- 单行注释,在被注释语句上方另起一行,使用
#
注释。方法内部多行注释使用***内容***
注释,注意与代码对齐。
*说明:*
*示例:*
```
```
- **与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持 英文原文即可。 **
*说明:*
*示例:*
```
```
-
代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改。
说明: 代码与注释更新不同步,不如没有注释。
示例:
-
谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。
说明: 代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。
示例:
-
对于注释的要求。
说明: 第一、能够准确反应设计思想和代码逻辑; 第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。
示例:
-
好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端,过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。
说明:
示例:
-
特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。
说明:
示例:
格式: # 标签 简要说明 (标记人, 标记时间, [预计处理时间]) # TODO 目前此逻辑未实现 (Lonersun, 2019/04/20, [2019/05/01]) # FIXME 此处有业务缺陷 (Lonersun, 2019/04/20, [2019/05/01]) # XXX 此处代码实现有问题,需要优化 (Lonersun, 2019/04/20, [2019/05/01]) # BUG 此处存在BUG,需解决 (Lonersun, 2019/04/20, [2019/05/01])
扩展:
TODO
:待办事项,备忘录。如果代码中有该标识,说明在标识处有功能代码待编写,待实现的功能在说明中会简略说明。FIXME
:如果代码中有该标识,说明标识处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说明中简略说明。XXX
:如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进,要改进的地方会在说明中简略说明。HACK
:如果代码中有该标识,说明标识处代码我们需要根据自己的需求去调整程序代码。BUG
: 如果代码中有该标识,说明标识处代码存在问题。
###(六)异常
- 业务代码中不捕获
Exception
对象,而是捕获更具体的对象。
*说明:* `Exception` 太笼统了, 最终不仅会捕获计划中的错误,还会捕获其他错误,这可能会掩盖代码中的错误,最好由单个非常高级的异常处理程序来处理,建议在每个异常捕获下面添加日志,以方便排查问题。要从 Python 内置的 Exception 类或特定的异常类(如 ValueError 或 KeyError)派
生出自定义异常。
*示例:*
```
反例:
try:
c = 1 + 2
except Exception as e:
pass
正例:
try:
c = 1 + 2
except KeyboardInterrupt as e:
logging.exception(e)
pass
```
-
对大段代码进行
try-except
,这是不负责任的表现。except
时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的except
尽可能进行区分 异常类型,再做对应的异常处理。说明:
示例:
-
捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。
说明:
示例:
-
有
try
块放到了事务代码中,捕获异常后,如果需要回滚事务,一定要注意手动回滚事务。说明:
示例:
###(七)日志
-
项目中无特殊情况,均使用
logging
日志模块。说明:
-
禁止使用
print
输出日志。说明: 在生产服务器一般不输出
print
的数据,如果用于调试,建议使用logging debug
打印日志。 -
根据不同的场景使用不同的日志级别。
说明: DEBUG 主要用于开发过程中打印一些运行信息。
INFO 打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
WARN 记录业务错误的情况。
ERROR 只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出
ERROR
级别。 -
生产环境禁止输出
DEBUG
日志;有选择地输出INFO
日志。说明:
Debug
日志仅在调试阶段使用,由于生产环境日志量较大,在生产环境一般会禁用掉Debug
日志。 -
捕获异常时,需记录案发现场信息和异常堆栈信息。
说明: 这样做为了能够通过日志,快速定位问题。
示例:
try: c = 1 + 2 except KeyboardInterrupt as e: logging.exception(e) pass
-
日志内容不建议使用
format
、+
等拼接。说明: it relies on former “%” format like string to provide lazy interpolation of this string using extra arguments given to the logger call.
示例:
反例: logging.error(u'=== error_code: {}, error_msg: {}===='.format(400, u"业务逻辑错误.")) 正例: logging.error(u'=== error_code: %s, error_msg: %s====', 400, u"业务逻辑错误.")
-
打印日志中禁止使用
[]
{}
去包装参数,以免引起歧义。说明:
示例:
反例: logging.error(u'=== error_code: [%s] ===', 400) # 通过打印出来的日志无法区分 error_code 的数据类型是 list 还是 int 正例: logging.error(u'=== error_code: %s ===', 400)
(八)其它
-
及时清理不再使用的代码段或配置信息。
说明: 对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余;如果不及时清理,在其他人开发过程中由于不知道是已废弃代码,去调用会造成不可预知的后果;也有可能存在多个实现不知道应该调用哪个的问题。
-
任何货币金额,均以最小货币单位且整型类型来进行存储。
说明: 如果采用浮点型,不同语言之间,数据的精度存在丢失问题。 示例:
PHP: echo 3.123456789012345; # 3.1234567890123 Python print 3.123456789012345 # 3.12345678901 Go: fmt.Print(3.123456789012345) # 3.123456789012345
(八)模块导入
-
禁止用通配符导入模块。
说明: 不清楚当前名称空间中存在哪些名称了,为了清楚起见,最好坚持使用常规导入方法。
示例:
反例: from my_module import * 正例: import my_module
-
导入模块的顺序应该是:内置模块,第三方模块,自定义模块。
说明: 每个导入应该独占一行,每个层级之间空一行。
示例:
import os # 内置 import time # 内置 import django # 第三方 import my_module # 自定义