Python – 代码规范

version

本 Python 代码编码规范参照 PEP8 所规定的代码规范。

这篇规范指南随着时间的推移而演变。

许多项目有自己的编码规范,在出现规范冲突时,项目自身的规范优先。

1、 前言

Guido 的一条重要的见解是代码阅读比写更加频繁。这里提供的指导原则主要用于提升代码的可读性,使得在大量的 Python 代码中保持一致。就像 PEP 20 提到的,“Readability counts”。

这是一份关于一致性的风格指南。这份风格指南的风格一致性是非常重要的。更重要的是项目的风格一致性。在一个模块或函数的风格一致性是最重要的。

然而,应该知道什么时候应该不一致,有时候编码规范的建议并不适用。当存在模棱两可的情况时,使用自己的判断。看看其他的示例再决定哪一种是最好的,不要羞于发问。

特别是不要为了遵守 PEP 约定而破坏兼容性!

几个很好的理由去忽略特定的规则:

  1. 当遵循这份指南之后代码的可读性变差,甚至是遵循 PEP 规范的人也觉得可读性差。
  2. 与周围的代码保持一致(也可能出于历史原因),尽管这也是清理他人混乱(真正的 Xtreme Programming 风格)的一个机会。
  3. 有问题的代码出现在发现编码规范之前,而且也没有充足的理由去修改他们。
  4. 当代码需要兼容不支持编码规范建议的老版本 Python。

2、代码布局

缩进

每一级缩进使用 4 个空格。

续行应该与其包裹元素对齐,要么使用圆括号、方括号和花括号内的隐式行链接来垂直对齐,要么使用挂行缩进对齐。当使用挂行缩进时,应考虑到第一行不应该有参数,以及使用缩进以区分自己时续行。

挂行缩进:在被括号括起来的语句中,左括号是这一行最后一个非空格字符,随后括号内的内容每一行进行缩进,直到遇到右括号。

制表符与空格

空格是首选的缩进方式。

制表符只能用于同样使用制表符缩进的代码保持一致。

Python3 不允许同时使用空格和制表符的缩进。

推荐

# 与左括号对齐
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# 用更多的缩进来与其他行区分
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# 挂行缩进应该再换一行
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)

不推荐

# 没有使用垂直对齐时,禁止把参数放在第一行
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# 当缩进没有与其他行区分时,要增加缩进
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

if语句

另外,当 if 语句的条件部分长到需要换行写时,增加一个空格,再增加一个左括号来创造一个对齐缩进的多行条件。

# 在条件判断的语句添加额外的缩进
if (this_is_one_thing and
        that_is_another_thing):
    do_something()

# 或者
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

多行结构

多行结构中的大括号、中括号和小括号的右括号可以单起一行,与多行结构的第一行第一个字符对齐,或者放在上一行的行尾

my_list = [
    1, 2, 3,
    4, 5, 6
]

# 或者

my_list = [
    1, 2, 3,
    4, 5, 6]

行的最大长度

所有行限制的最大字符数为 72

较长的代码行选择 Python 在小括号、中括号以及大括号中的隐式续行方式。通过小括号内表达式的换行方式将长串折成多行。这种方式应该优先使用。

反斜杠有时候仍然有用,比如比较长的多个 with 状态语句,或者 assert 语句

with open('/path/to/some/file/you/want/to/read') as file_1, \
     open('/path/to/some/file/being/written', 'w') as file_2:
    file_2.write(file_1.read())

assert x == 0, "x must be 0" \
    ", more infomation"

二元运算符换行

在使用运算符的时候如果有换行的需求,以往推荐的风格是在二元运算符之后中断,但这会影响可读性。原因有二:

  1. 操作符一般分布在屏幕上的不同列
  2. 每个运算符被移到了操作数的上一行
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

为了解决这种可读性的问题,遵循相反的约定:在二元运算符之前中断。

在Python中,允许在二元运算符之前或之后中断,只要代码约定一致。建议使用在二元运算符之前中断

income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

空行

顶层函数和类的定义,前后用两个空行隔开。
类里的方法定义用一个空行隔开。
相关的功能组可以用额外的空行隔开(谨慎使用)。
一堆相关的单行代码之间的空白行可以省略。
在函数中使用空行来区分逻辑段(谨慎使用)。

源文件编码

PEP8 中的描述为:对于 Python3 和更高的版本,Python 标准库中的所有标识符必须使用 ASCII 标识符,并在可行的情况下使用英文单词。此外,字符串文字和注释也必须是 ASCII。作者的名字如果不使用拉丁字母拼写,必须提供一个拉丁字母的音译。

导入

导入通常在分开的行。当然,在同一行导入也是被允许的。
导入总是位于文件的顶部,在模块注释和文档字符串之后,在模块的全局变量与常量之前。
导入应按照以下顺序分组,每组之间加入空行:

  1. 标准库导入
  2. 第三方库导入
  3. 本地库\包特定导入
# 推荐
import os
import sys

# 不推荐
import os, sys

# 可行
from subprocess import Popen, PIPE

# 分组
import os

import numpy as np
import tensorflow as tf

from mymodule import MyObject

推荐使用绝对路径导入,使用绝对路径可读性更佳并且性能更好。
然而,显式的指定相对导入路径是使用绝对路径的一个可接受的替代方案,特别是在处理使用绝对路径导入不必要冗长的复杂包布局时。
标准库要避免使用复杂的包引入结构,而总是使用绝对路径。
不应该使用隐式相对路径导入(注:在 Python3 中已删除隐式相对路径导入)

# 推荐
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

# 显式相对导入路径
from . import sibling
from .sibling import example

避免通配符的导入(from <module> import *),因为这样做会不知道命名空间中存在哪些名字,会使得读取接口和许多自动化工具之间产生混淆。对于通配符的导入,有一个防御性的做法,即将内部接口重新发布为公共API的一部分。
当以这种方式重新发布名字时,以下关于公共和内部接口的准则仍然适用。

模块级的呆名

dunder name(呆名)即名字里面有两个前缀下划线和两个后缀下划线,例如 __all____author____version__。这些呆名应该放在文档字符串的后面,以及除 from __future__ imports 之外的 import 表达式前面。Python 要求将来在模块中的导入,必须出现在除文档字符串之外的其他代码之前。

"""This is the example module.

This module does stuff.
"""

from __future__ import barry_as_FLUFL

__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

import os
import sys

3、字符串引号

在 Python 中,单引号和双引号字符串是相同的。PEP 不会为这个给出建议。可以自己定义一个规则并从一而终。当一个字符串包含单引号或双引号字符时,使用和最外层不同的符号来避免使用反斜杠(转义),从而提高可读性。

对于三引号字符串,总是使用双引号字符串。(与 PEP257 中的文档字符串约定保持一致)

4、表达式和语句中的空格

不能忍受的事情

在下列情况下,避免使用无关的空格:

  1. 紧跟在小括号,中括号或者大括号后

    # 建议
    spam(ham[1], {eggs: 2})
    # 不建议
    spam( ham[ 1 ], { eggs: 2 } )
  2. 在尾随逗号和右括号之间

    # 建议
    foo = (0,)
    # 不建议
    bar = (0, )
  3. 紧贴在逗号、分号或者冒号之前

    # 建议
    if x == 4: print x, y; x, y = y, x
    # 不建议
    if x == 4 : print x , y ; x , y = y , x
  4. 但是,在切片中冒号的作用与二元运算符一样,在两边应该有相同数量的空格(把它当做优先级最低的操作符)。在扩展的切片操作中,所有的冒号必须有相同的间距。例外情况:当一个切片参数被省略时,空格就被省略了

    # 建议
    ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
    ham[lower:upper], ham[lower:upper:], ham[lower::step]
    ham[lower+offset : upper+offset]
    ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
    ham[lower + offset : upper + offset]
    # 不建议
    ham[lower + offset:upper + offset]
    ham[1: 9], ham[1 :9], ham[1:9 :3]
    ham[lower : : upper]
    ham[ : upper]
  5. 紧贴在函数参数的左括号之前

    # 建议
    spam(1)
    # 不建议
    spam (1)
  6. 紧贴索引或者切片的左括号之前

    # 建议
    dct['key'] = lst[index]
    # 不建议
    dct ['key'] = lst [index]
  7. 为了和另一个赋值语句对齐,在赋值运算符附件加多个空格

    # 建议
    x = 1
    y = 2
    long_variable = 3
    # 不建议
    x             = 1
    y             = 2
    long_variable = 3

其他建议

  1. 避免在尾部添加空格。因为尾部的空格通常都看不见,会产生混乱。

    • 比如,一个反斜杠后面跟一个空格的换行符,不算续行标记。
    • 有些编辑器不会保留尾空格
  2. 总是在二元运算符两边加一个空格:

    1. 赋值(=
    2. 增量赋值(+=-=
    3. 比较(==<>!=<><=>=innot inisis not
    4. 布尔逻辑(andornot
  3. 如果使用具有不同优先级的运算符,请考虑在具有最低优先级的运算符周围添加空格。有时需要通过自己来判断

    • 但是,不要使用一个以上的空格,并且在二元运算符的两边使用相同数量的空格
    # 推荐
    i = i + 1
    submitted += 1
    x = x*2 - 1
    hypot2 = x*x + y*y
    c = (a+b) * (a-b)
    # 不推荐
    i=i+1
    submitted +=1
    x = x * 2 - 1
    hypot2 = x * x + y * y
    c = (a + b) * (a - b)
  4. 函数注释应使用冒号的正常规则,并始终在 -> 箭头前后有空格

    # 推荐
    def munge(input: AnyStr): ...
    def munge() -> PosInt: ...
    # 不推荐
    def munge(input:AnyStr): ...
    def munge()->PosInt: ...
  5. 在指定关键字参数或者默认参数值的时候,不要在 = 附近加上空格

    # 推荐
    def complex(real, imag=0.0):
        return magic(r=real, i=imag)
    # 不推荐
    def complex(real, imag = 0.0):
        return magic(r = real, i = imag)

    但是,在将参数注释和默认值组合的时候,请在 = 前后加上空格

    # 推荐
    def munge(sep: AnyStr = None): ...
    def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
    # 不推荐
    def munge(input: AnyStr=None): ...
    def munge(input: AnyStr, limit = 1000): ...
  6. 复合语句(同一行中多个语句)通常是不允许的

    # 推荐
    if foo == 'blah':
        do_blah_thing()
    do_one()
    do_two()
    do_three()
    # 不推荐
    if foo == 'blah': do_blah_thing()
    do_one(); do_two(); do_three()

    另外也请不要这样

    if foo == 'blah': do_blah_thing()
    for x in lst: total += x
    while t < 10: t = delay()

    绝对别这样

    if foo == 'blah': do_blah_thing()
    else: do_non_blah_thing()
    
    try: something()
    finally: cleanup()
    
    do_one(); do_two(); do_three(long, argument,
                                 list, like, this)
    
    if foo == 'blah': one(); two(); three()

    唯一可以写到同一行的是 Ellipsis,即...

    def foo(): ...

5、何时使用尾随逗号

尾随逗号通常是可选的。但当你需要创建只有一个元素的元组是,它是必填项。

# 正确
FILES = ('setup.cfg',)
# 错误
FILES = 'setup.cfg',

当尾随逗号是冗余的时候,在使用版本控制系统(VCS)时如果值随着时间的推移而扩展时,会非常有用。
使用的方式是将每个值本身放在一行,始终添加尾随逗号,并在下一行添加右括号。但是,与结束分隔符在同一行上使用尾随逗号没有意义。

# 建议
FILES = [
    'setup.cfg',
    'tox.ini',
    ]
initialize(FILES,
           error=True,
           )
# 不建议
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)

6、注释

与代码相矛盾的注释比没有注释还糟!当代码更改时,优先更新对应的注释!
注释应该是完整的句子。如果一个注释是一个短语或句子,它的第一个单词应该大写,除非它是以小写字母开头的标识符。
如果注释很短,结尾的句号可以省略。
块注释一般由完整句子的一个或多个段落组成,并且每句话结束有个句号
在句尾结束的时候应该使用两个空格

当用英文书写时,遵循 Strunk and White 的书写风格。
在非英语国家的 Python 程序员,请使用英文写注释,除非你 120% 确定你的代码不会被使用其他语言的人使用。
如果不使用英文写注释,请注意不要混用语言,要么纯英文,要么纯某种语言。

块注释

块注释通常适用于跟随它们的某些(或全部)代码,并缩进到与代码相同的级别。块注释的每一行开头使用一个 # 和一个空格(除非块注释内部缩进文本)。
块注释内部的段落通过只有一个 # 的空行分隔

可以使用 CodeTags,推荐使用 TODOFIXME

行内注释

有节制地使用行内注释。
行内注释是与代码语句同行的注释。
行内注释与代码至少要有两个空格分隔。
注释由一个 # 和一个空格开始。
如果代码非常明显,行内注释是不必要的。

# 不必要的
x = x + 1                 # Increment x
# 有用的
x = x + 1                 # Compensate for border

文档字符串

编写好的文档说明(也叫 Docstrings)的约定在 PEP257 中

  • 要为所有的公共模块、函数、类以及方法编写文档说明

  • 非公共的方法没有必要,但是应该有一个描述方法具体作用的注释。这个注释应该在 def 那一行之后。

  • PEP257 描述了写出好的文档说明相关的约定。特别需要注意的是,多行文档说明使用的结尾三引号应该自成一行

    """Return a foobang
    
    Optional plotz says to frobnicate the bizbaz first.
    """
  • 对于单行的文档说明,尾部的三引号应该和文档同一行

    """Single Line Docstrings"""

7、命名规范

Python 库的命名规范很乱,从来没能做到完全一致

推荐使用以下的命名规范

最重要的原则

暴露给用户的API接口的命名,应该遵循反映使用场景而不是实现的原则

描述:命名风格

  • b(单个小写字母)
  • B(单个大写字母)
  • lowercase
  • lower_case_with_underscores
  • UPPERCASE
  • UPPER_CASE_WITH_UNDERSCORES
  • CapitalizedWords
    • 注:当首字母大写的风格中用到缩写时,所有缩写的字母用大写
    • 例如:HTTPServerErrorHttpServerError要好

8、约定俗成:命名约定

应避免的名字

  • 尽量避免使用 l(小写的 L)、O(大写的 O)或者 I(大写的 I)作为单字符变量名
  • 在某些字体里,这些字符与 1 和 0 的区分度比较低
  • 注:如果确保整个团队都使用区分度比较大的等距字体,也可以使用

包名和模块名

  • 模块应该用简短全小写的名字,如果为了提高可读性,下划线也是可以用的(尽量避免)
  • Python 包名也应该使用简短全小写的名字,但不建议用下划线
  • 当使用 C 或者 CPP 编写了一个依赖于提供高级接口的 Python 模块的扩展模块,这个 C/CPP 模块需要一个下划线前缀,如 _socket

类名

  • 类名一般使用首字母大写的约定。
  • 在接口被文档化并且主要被用于调用的情况下,可以使用函数的命名风格替代
  • 推荐:使用 CapitalizedWords 命名风格

类型变量名

  • PEP484 中引入的类型变量名称通常应使用 CapWords 前缀短名字,如:TAnyStrNum
  • 建议在用于相应的声明中添加 _co_contra 到变量来使用。
from typing import TypeVar

VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)

异常名

  • 因为一般来说异常都是类,所以类的命名方法在这里也适用。
  • 不过,需要在异常类的名字后面加上 Error 后缀。

全局变量名

  • 约定和函数命名规则一致
  • 通过 from <module> import * 导入的模块应使用 __all__ 来防止全局变量对外暴露,或者使用在全局变量前加下划线的方式

函数名及变量名

  • 函数名和变量名应该采用 lower_case_with_underscores 风格
  • 大小写混合仅在为了兼容原来主要以大小写混合风格的情况下使用(如 threading.py),保持向后兼容性

函数和方法参数

  • 始终要将 self 作为实例方法的第一个参数
  • 始终要将 cls 作为类方法的第一个参数
  • 如果函数的参数名和已有的关键词冲突,在最后加单下划线比缩写或者随意拼写更好,如:class_clss 要好

方法名和属性

  • 命名风格与函数名及变量名一致

  • protected 方法和属性前使用单下划线

  • private 方法和属性前使用双下划线

    • Python 中对于加了双下划线的实例方法和属性,进行了命名转换,例如

      class Foo:
          def __init__():
              self.__name = "Bar"
      
      foo = Foo()
      print(foo.__name)  # 报错
      print(foo._Foo__name)  # 成功

常量

  • 常量通常定义在模块中,并且使用 UPPER_CASE_WITH_UNDERSCORES 风格
  • 例如:LEVELMAX_OVERFLOW

继承的设计

  • 始终要考虑到一个类的方法和属性应该是共有还是非公有。如果存在疑问,那就选非公有,因为将一个非公有变量转为公有比反过来容易
  • 公共属性是那些与类无关的、用户使用的属性,并承诺避免做出向后不兼容的更改。非公共属性是那些不打算让第三方使用的属性,不需要承诺非公有属性不会被修改或被删除
  • 我们不使用 private(私有)这个说法,因为在 Python 中并没有真正的私有属性

以下是 Pythonic 的准则

  • 公共属性不应该有前缀下划线
  • 如果公共属性名和关键字冲突,请在属性名附加单个尾随下划线。这比缩写和随意拼写好很多
  • 对于单一的公有数据属性,最好直接对外暴露它的变量名,而不是通过负责的存取器(accessor)或突变(mutator)方法
  • 如果类打算用来继承,并且这个类有不希望子类使用的属性,就要考虑使用双下划线前缀并且没有后缀下划线的命名方式

9、公共和内部的接口

任何向后兼容保证只适用于公共接口(API)
因此,用户清晰地区分公共接口和内部接口非常重要

文档化的接口被认为是公开的,除非文档明确声明它们是临时或内部接口,不受通常的向后兼容性保证。所有未记录的接口都应该是内部的

为了更好地支持内省(introspection),模块应该使用 __all__ 属性显式地在它们的公共API中声明名称。将 __all__ 设置为空列表表示模块没有公共API
即使通过 __all__ 设置过,内部接口依然需要单个下划线前缀

如果一个命名空间(包、模块、类)被认为是内部的,那么包含它的接口也应该被认为是内部的

导入的名称应该始终被视作是一个实现的细节,其他模块必须不能间接访问这样的名称,除非它是包含它的模块中有明确的文档说明的 API,如 os.path 或者是从一个包里从子模块公开函数接口的 __init__ 模块。

10、编程建议

  • 代码应该用不损害其他 Python 实现的方法去编写(Pypy、Jython、CPython 等)
    比如,不要依赖于在CPython中高效的内置字符链接语句 a += b。这种优化甚至在 CPython 中都是脆弱的(只适用于某些类型),并且没有出现在不使用引用计数的实现中。在性能要求比较高的库中,可以用 "".join() 来代替。

  • None 这样的单例对象的比较应该始终用 isis not,而不要使用 ==

  • 使用 is not 运算符,而不是 not ... is。虽然两者在功能上完全相同,但是前者更易于阅读,所以优先考虑

    # 推荐
    if foo is not None: ...
    # 不推荐
    if not foo is None: ...
  • 当使用富比较(rich comparisons)实现排序操作时,最好实现全部的六个操作符(__eq__, __ne__, __lt__, __le__, __gt__, __ge__
    为最大程度减少这一过程的开销,functools.total_ordering() 装饰器提供了用于生成缺少的比较方法的工具。
    PEP 207 指出Python实现了反射机制。因此,解析器会将 y > x 转变为 x < y,将 y >= x 转变为 x <= y,也会转换 x == yx != y 的参数。sort()min() 方法确保使用 < 操作符,max() 使用 > 操作符。然而,最好还是实现全部六个操作符,以免在其他地方出现冲突。

  • 始终使用 def 表达式,而不是通过赋值语句将 lambda 表达式绑定到一个变量上。

    # 推荐
    def f(x): 
        return 2*x
    # 不推荐
    f = lambda x: 2*x

    第一个形式意味着生成的函数对象名称是 f 而不是泛型 <lambda>,这在回溯和字符串显示的时候更有用。赋值语句的使用消除了 lambda 表达式优于显式 def 表达式的唯一优势。

  • Exception 继承异常,而不是从 BaseException。直接继承 BaseException 的异常适用于几乎不用来捕捉的异常。

  • 适当地使用异常链接。在 Python3 中,为了不丢失原始的根源,可以显式地指定 raise X from Y 作为替代。

  • 在 Python2 中抛出异常时,使用 raise ValueError("message") 而不是更老的形式 raise ValueError, "message"

  • 当捕获到异常时,如果可以的话写上具体的异常名,而不是只用一个 except 块

    try:
        import platform_specific_module
    except ImportError:
        platform_specific_module = None

    如果只有一个 except 块,将会捕获到 SystemExitKeyboardInterrupt 异常,这样会很难通过 Control-C 中断程序,而且会掩盖掉其他问题。如果想捕获所有异常,显式地使用 raise Exception(只有 except 等价于 except BaseException)。

    • 两种情况不应该只使用 except 块
    • 1、如果异常处理的代码会打印或者记录 log,至少让用户知道发生了一个错误
    • 如果代码需要做清理工作,使用 raise...try...finally 能很好处理这种情况并且能让异常继续上浮。
  • 当给捕捉到的异常绑定一个名字时,推荐使用在 Python2.6 中加入的显式命名绑定语法:

    try:
        process_data()
    except Exception as e:
        raise DataProcessingFailedError(str(e))

    为了避免与原来基于逗号分隔的语法出现歧义,Python3 只支持这种语法

  • 当捕捉操作系统的错误时,推荐使用 Python3.3 中 errno 内定数值指定的异常等级

  • 另外,对于所有的 try...except 语句块,在 try 语句中只填充必要的代码,这样可以避免掩盖掉 bug。

    # 推荐
    try:
        value = collection[key]
    except KeyError:
        return key_not_found(key)
    else:
        return handle_value(value)
    # 不推荐
    try:
        # Too broad!
        return handle_value(collection[key])
    except KeyError:
        # Will also catch KeyError raised by handle_value()
        return key_not_found(key)
  • 当代码片段局部使用了某个资源时,使用 with 表达式来确保这个资源使用完之后被清理干净。用 try...finally 也可以。

  • 无论合适获取和释放资源,都应该通过单独的函数或者方法调用上下文管理器,例如

    # 推荐
    with conn.begin_transaction():
        do_stuff_in_transaction(conn)
    # 不推荐
    with conn:
        do_stuff_in_transaction(conn)
  • 返回的语句保持一致,函数中的返回语句都应该返回一个表达式,或者都不返回
    如果一个返回语句需要返回一个表达式,那么在没有值可以返回的情况下,需要用 return None 显式指明,并且在函数的最后,显式指定一条返回语句。

  • 使用字符串方法代替字符串模块
    字符串方法总是更快,并且和 unicode 字符串分享相同的 API

  • 使用 .startwith().endwith() 代替通过字符串切割的方法去检测前缀和后缀
    这两个函数更干净,出错几率更小

    # 推荐
    if foo.startswith('bar'): ...
    # 不推荐
    if foo[:3] == 'bar': ...
  • 对象类型的比较应该用 isinstance() 而不是直接比较 type

    # 推荐
    if isinstance(obj, int): ...
    # 不推荐
    if type(obj) is type(1): ...
  • 空序列为 False

    # 推荐
    if not seq:
    if seq:
    # 不推荐
    if len(seq):
    if not len(seq):
  • 不要编写依赖于大量尾随空格的字符串文本,这种尾随空白在视觉上无法区分。一些编辑器会自动去掉它们

  • 不要用 ==, is, is not 去和 True 或者 False 比较

    # 推荐
    if greeting: ...
    # 不推荐
    if greeting == True: ...
    # 更糟
    if greeting is True: ...
  • 在使用 try...finally 的时候,请避免将流程控制语句 return, break, continue 放在 finally 语句块中。这是因为在 finally 语句执行时隐式取消任何异常。

    # 不推荐
    def foo():
        try:
            1 / 0
        finally:
            return 42

11、函数注释

随着 PEP484 的引入,函数注释的风格规范有些变化

  • 为了向前兼容,在 Python3 代码中的函数注释应使用 PEP484 的语法规则

  • 不在鼓励使用之前在 PEP 中推荐的实验式样式

  • Python 的标准库代码应该保守使用这种注释,但新的代码或者大型的重构可以使用这种注释

  • 如果代码希望对功能注释有不同的用途,建议在文件的顶部增加一个这种形式的注释

    # type: ignore

    这会告诉检查器忽略所有的注释。

  • 像 linters 一样,类型检测器是可选的可独立的工具。默认情况下,Python 解释器不应该因为类型检查而发出任何消息,也不应该基于注释改变它们的行为。

  • 不想使用类型检测的用户可以忽略他们。然而,第三方库的用户可能希望在这些库上运行类型检测。为此,PEP484 建议使用存根文件类型:.pyi 文件,这种文件类型相比于 .py 文件会被类型检测器读取。存根文件可以和库一起,或者通过 typeshed repo 独立发布(通过库作者的许可)

  • 对于需要向后兼容的代码,可以以注释的形式添加函数注释。

12、变量注释

PEP526 引入了变量注释,样式建议与上述函数注释上的样式建议类似

  • 模块级变量、类和属性以及局部变量的注释在冒号之后应该具有单个空格

  • 冒号前应没有空格

  • 如果赋值具有右侧,则 = 的两则应只有一个空格

    # 推荐
    code: int
    
    class Point:
        coords: Tuple[int, int]
        label: str = ''
    # 错误
    code:int  # No space after colon
    code : int  # Space before colon
    
    class Test:
        result: int=0  # No spaces around equality sign
  • 尽管 PEP526 是在 Python3.6 的时候接受的,但是变量注释语法是所有版本的 Python 上存根文件的首选语法

13、其他规范

这些规范并非强制,仅为建议

  • 在每个包里面最好有一个 __init__.py 文件,虽然这在 Python3 之后并非必须。这一文件能够有效地管理包

  • 尽量只使用简单的列表/字典/集合推导式
    过于复杂的推导式只会降低代码的可读性和可维护性

  • if...else... 尽量不超过3层,如果超过,尽量考虑转化为 if...elif...else...,或者使用设计模式

  • 定义对象、变量、新建数组等涉及空间的操作尽量在循环体外部执行,以降低资源消耗

  • 避免出现重复代码,即 DRY 原则(Don't Repeat Yourself

  • 不使用的变量可以用单下划线 _ 来接收,比如

    for _ in range(5):
        print("Hello World")
  • 不推荐使用 ;

  • 格式化字符串统一使用 f-string,它比旧的写法性能更高,可读性也更好。


* 参考文章:

You may also like...

发表评论

您的电子邮箱地址不会被公开。