PythonPEP8编码规范

Python 编码规范

(一)命名规范

  1. 变量、参数、方法命名采用 lower_snake_case(小蛇式)命名方法。

    示例:

    反例:
    
    Name、NAME、SEND_MESSAGE、 Send_Message、SendMessage、SENDMESSAGE
    
    正例:
    
    name、update_phone
    
  2. 类命名采用 UpperCamelCase(大驼峰式)命名方法。

    示例:

    反例:
    
    accountService
    
    正例:
    
    AccountService
    
  3. 常量命名采用 UPPER_SNAKE_CASE(大蛇式)命名方法。

    说明: 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

    示例:

    反例:
    
    max_stock_count
    
    正例:
    
    MAX_STOCK_COUNT
    
  4. 避免使用单字母变量,尽量细化变量含义,命名要有意义,做到见名知义。

    说明: 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。

    示例:

    给姓名、性别赋值
    
    反例:
    
    a, b = "张三", "男"
    
    正例:
    
    name, sex = "张三", "男"
    
  5. 禁止出现单词拼写错误的情况。

    示例:

    校验单词
    
    反例:
    
    vilidate
    
    正例:
    
    validate
    
  6. 对于接收多个返回值的情况,接收返回值时只接收要用的,其它的可以用 _ 忽略。

    示例:

    程序中只用到了姓名,其他没用到
    
    反例:
    
    name, sex, age = fun()
    
    正例:
    
    name, _, _ = fun()
    
  7. 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。

    说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用。

    示例:

    给变量 ‘电话’ 赋值
    
    反例:
    
    dianhua = "8722334"  #(中文拼音)
    
    正例:
    
    telephone = "8722334"
    
  8. 使用缩写进行命名时,要采用公认的、约定俗成的缩写,切不可自造缩写。

    说明: 杜绝完全不规范的缩写,避免望文不知义。

    示例:

    给变量 ‘电话’ 赋值
    
    反例:
    
    dh = "8722334"  #(电话两字的首字母)
    tp = 8722334  #(telephone 拆解)
    
    正例:
    
    tel = "8722334"
    
  9. 避免在名称中包含易混淆的字符。

    说明:

    示例:

    1(数字 1)和 l(小写字母L)
    1和I(大写字母i)
    0(零)和O(大写字母o)
    2(数字2)和Z
    
  10. 采用名词或者形容词来命名变量, 对于集合变量,使用类型或者名次复数s作为后缀。

    说明: 变量一般情况下建议使用名词、名字组合或者形容词,因为变量一般形容的是一种事物或者事物的属性,所以用名词或者名词组合更容易让人理解,而形容词一般用于 Bool 类型的变量。

    示例:

    apple = "苹果"
    apples = ["苹果1", "苹果2"]
    
  11. 在一个代码块中,变量作用域越大,命名应该越长越精确。相应的,变量名越短,就应该保持变量的作用域越小。

  12. 在相关的代码中保持全局命名风格的一致,变量命名前后用词需统一。

    说明: 在同一项目中,如果在某一模块中使用 state 作为状态的变量名,那么尽可能的在其他模块中也使用 state 作为状态的变量名,切不可即存在 state, 又存在 status 等等

    示例:

    反例:
    a.py
    
    state = 100
    
    b.py
    
    status = 100
    
    c.py
    
    stat = 100
    
    
    正例:
    
    a.py
    b.py
    c.py
    
    state = 100
    
  13. 变量、参数命名时不建议使用内置函数名称。

    说明:

    示例:

    反例:
        len = 123
        id = "123"
        map = "222"
        dict = {}
    
  14. 命名规约。

    说明:

    方法命名: 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) …

  15. 【推荐】下划线、双下划线的使用。

    说明:

    前置单下划线: _var 用来表示该名称仅在内部使用。一般对 Python 解释器没有特殊含义(通配符导入除外),只能作为对程序员的提示。

    后置单下划线: var_ 命名约定,用于避免与 Python 关键字发生命名冲突。

    前置双下划线: __var 在类环境中使用时会触发名称改写,对 Python 解释器有特殊含义。

    前后双下划线: __var__ 表示由 Python 语言定义的特殊方法。在自定义的属性中要避免使用这种命名方式。

    单下划线: _ 单下划线有时用作名称,来表示变量是临时的或无关紧要的。

(二)常量/枚举值规范

  1. 代码中所有的枚举值必须在常量中定义。

    说明: 枚举值在常量中定义,能做到统一管理,并且能通过常量名称知晓枚举的含义。

    示例:

    反例:
    
    if sex == 1:
        return False
    
    正例:[能通过常量的名称知晓所指的含义]
    
    if sex == constants.MALE:
        return False
    
  2. 不要使用一个常量类或者常量文件维护所有常量,按常量功能进行归类,分开维护。

    说明: 大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。

  3. 在定义枚举值时要预留好扩展空间。

    说明: 预留扩展空间方便日后需求增加对整体结构不造成影响。

    示例:

    以外卖配送单据为例,如果第一版需求配送单状态定义为:
    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  # 配送员配送完成
    
  4. 枚举值不推荐使用 0NoneFalse 等字段。

    说明: 在做逻辑判断时,需要更多的代码去验证是否满足需求。

    示例:

    SEX_MAN = 0
    
    account_sex = False
    
    if account_sex == SEX_MAN:
        print True
    else:
        print False
    
    结果为True
    

(三)代码格式

  1. 不允许无脑式的复制、粘贴代码。

    说明: 无脑式的复制代码,会把一些用不到的东西也会复制过来,对后面维护的人简直是灾难。

  2. 任何二目、三目运算符的左右两边都需要加一个空格。

    说明: 运算符包括赋值运算符 =、逻辑运算符、加、减、乘、除符号等。

    示例:

    反例:
    
    c=a+b
    
    正例:
    
    c = a + b
    
  3. 采用 4 个空格缩进,禁止使用 Tab 字符。

    说明: 如果使用 Tab 缩进,必须设置 1 个 Tab 为 4 个空格。

  4. 每一个逻辑代码块结束时要增加一个空行。

    说明: 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。

    示例:

    反例:
    
    if a < 10:
        pass
    elif a == 10:
        pass
    else:
        pass
    
    
    正例:
    
    if a < 10:
        pass
    
    elif a == 10:
        pass
    
    else:
        pass
    
  5. 每一个 py 文件结束时,要增加一个空行,不允许多个空行。

    说明:

  6. 参数、包、模块未被使用时要清理掉。

    说明: 只引入在代码中使用的

    示例:

    反例:
    import time
    from bson import ObjectId
    import logging
    
    print time.time()
    
    正例:
    import time
    
    print time.time()
    
  7. 单行字符数限制不超过 120 个,超出需要换行。

    说明: PEP8 要求每行代码不超过80个字符;实际上是79个。 缘由是:我们已经不是使用 VT100 终端编程的年代了,我们有了更大,更高分辨率的屏幕。 目前我们要求单行代码长度不允许超过120个字符。

  8. 方法参数在定义和传入时,多个参数逗号后边必须加空格。

    说明:

    示例:

    反例:
    
    fun(a,b,c)
    
    正例:
    
    fun(a, b, c)
    
  9. 每个 py 文件在头行必须添加 # -- coding:utf-8 --。

    说明:

  10. 在 Python2 版本中文字符前必须加 u

    说明:

    示例:

    反例:
       name = "张三"
    
    正例:
       name = u"张三"
    
  11. 自然语言使用双引号,机器标示使用单引号,正则表达式使用原生的双引号,文档字符串 (docstring) 使用三个双引号。

    说明:

    示例:

    
    正例:
        name = u"张三"
        data['name'] = name
        phone_regex = r"^1\d{10}$"
    
  12. 模块级函数和类定义之间空两行;类成员函数之间空一行;函数中可以使用空行分隔出逻辑相关的代码。

    说明:

    示例:

    class A(object):
    
        def __init__(self):
            pass
    
    def main(self):
       pass 
    

(四)控制语句

  1. 表达异常的分支时,少用 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` 的嵌套,可读性提高,代码更简洁,逻辑清晰;
- 考虑到真实发送的场景要远大于模拟发送的场景,所以把判断顺序进行了调整,提高了效率。

```
  1. 不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

    说明: 很多 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
    
  2. 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 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
    
  3. 函数中的所有 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
    
  4. 判断某字符是否在某范围内时,建议范围数据使用 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. 类、类属性、类方法、方法必须要有注释,使用 """内容""" 格式,不要使用 # 内容
*说明:* 注释除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。

*示例:*

``` 


```

  1. 单行注释,在被注释语句上方另起一行,使用 # 注释。方法内部多行注释使用 ***内容*** 注释,注意与代码对齐。
*说明:* 

*示例:*

``` 


```

  1. **与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持 英文原文即可。 **
*说明:* 

*示例:*

``` 


```

  1. 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改。

    说明: 代码与注释更新不同步,不如没有注释。

    示例:

  2. 谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。

    说明: 代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。

    示例:

  3. 对于注释的要求。

    说明: 第一、能够准确反应设计思想和代码逻辑; 第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。

    示例:

  4. 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端,过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。

    说明:

    示例:

  5. 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。

    说明:

    示例:

    格式:
    
    # 标签 简要说明 (标记人, 标记时间, [预计处理时间])
    
    # 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: 如果代码中有该标识,说明标识处代码存在问题。

###(六)异常

  1. 业务代码中不捕获 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


```

  1. 对大段代码进行 try-except,这是不负责任的表现。except 时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 except 尽可能进行区分 异常类型,再做对应的异常处理。

    说明:

    示例:

  2. 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请 将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的 内容。

    说明:

    示例:

  3. try 块放到了事务代码中,捕获异常后,如果需要回滚事务,一定要注意手动回滚事务。

    说明:

    示例:

###(七)日志

  1. 项目中无特殊情况,均使用 logging 日志模块。

    说明:

  2. 禁止使用 print 输出日志。

    说明: 在生产服务器一般不输出 print 的数据,如果用于调试,建议使用 logging debug 打印日志。

  3. 根据不同的场景使用不同的日志级别。

    说明: DEBUG 主要用于开发过程中打印一些运行信息。

    INFO 打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。

    WARN 记录业务错误的情况。

    ERROR 只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出 ERROR 级别。

  4. 生产环境禁止输出 DEBUG 日志;有选择地输出 INFO 日志。

    说明: Debug 日志仅在调试阶段使用,由于生产环境日志量较大,在生产环境一般会禁用掉Debug 日志。

  5. 捕获异常时,需记录案发现场信息和异常堆栈信息。

    说明: 这样做为了能够通过日志,快速定位问题。

    示例:

    try:
        c = 1 + 2
    
    except KeyboardInterrupt as e:
        logging.exception(e)
        pass
    
  6. 日志内容不建议使用 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"业务逻辑错误.")
    
  7. 打印日志中禁止使用 [] {} 去包装参数,以免引起歧义。

    说明:

    示例:

    反例:
    logging.error(u'=== error_code: [%s] ===', 400)
    # 通过打印出来的日志无法区分 error_code 的数据类型是 list 还是 int
    
    正例:
    logging.error(u'=== error_code: %s ===', 400)
    

(八)其它

  1. 及时清理不再使用的代码段或配置信息。

    说明: 对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余;如果不及时清理,在其他人开发过程中由于不知道是已废弃代码,去调用会造成不可预知的后果;也有可能存在多个实现不知道应该调用哪个的问题。

  2. 任何货币金额,均以最小货币单位且整型类型来进行存储。

    说明: 如果采用浮点型,不同语言之间,数据的精度存在丢失问题。 示例:

    PHP:
    echo 3.123456789012345; # 3.1234567890123
    
    Python
    print 3.123456789012345  # 3.12345678901
    
    Go:
    fmt.Print(3.123456789012345)  # 3.123456789012345
    

(八)模块导入

  1. 禁止用通配符导入模块。

    说明: 不清楚当前名称空间中存在哪些名称了,为了清楚起见,最好坚持使用常规导入方法。

    示例:

    反例
    
    from my_module import *
    
    正例
    
    import my_module
    
  2. 导入模块的顺序应该是:内置模块,第三方模块,自定义模块。

    说明: 每个导入应该独占一行,每个层级之间空一行。

    示例:

    import os  # 内置
    import time  # 内置
    
    import django  # 第三方
    
    import my_module  # 自定义
    

PythonNotes

1269 Words

创建于 2020-07-20 15:57 +0800

修改于 2020-07-20 15:57 +0800