Python编码规范(PEP8)

Python编码规范(PEP8)

PEPPython Enhancement Proposal的缩写,翻译过来就是Python增强建议书。

一.代码布局


1.1 缩进

每个缩进层次为4个空格。

连续行包装元素的两种方案:

  1. 隐式续行:续行与圆括号、方括号或花括号的左括号对齐;
  2. 悬挂缩进:首行没有参数,续行应该多缩进一级以便和正常的缩进区分。
# 隐式续行
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)
    
# 悬挂缩进可以缩进到4个空格以外的其他空格
foo = long_function_name(
  var_one, var_two,
  var_three, var_four)
1.2 Tab 或 空格

  • 空格是首选的缩进方法。

  • 唯一的一种情况下建议使用Tab:之前的代码已经使用了Tab,为了保持一致性。

注:Python3不允许混合使用Tab键和空格进行缩进。

1.3 单行最大长度

  • 单行最大长度不超过79个字符。

  • 为了使较长的文本块具有较少的结构限制(文档字符串或者注释),单行最大长度应该限制为72个字符。

  • 换行的首选方法是使用Python的隐含续行机制,即在小括号、中括号、大括号内部自动续行。很长的行可以通过把表达式包含在圆括号内,实现自动续行,相比使用反斜杠\续行,这个方法应该更为推荐。

  • 有时候反斜杠仍然是适合的。例如,with语句不能使用隐式续行,因此可以接受反斜杠。

1.4 二元操作符换行

在二元操作符之前和之后换行两种方式都是被允许的,但是从可读性的角度,建议在二元操作符之前换行。

  1. 运算符趋向于分散在屏幕上的不同列上,并且每个运算符都从其操作数移至上一行。但是,这降低了代码的可读性:

    # 二元操作符之后换行
    income = (gross_wages +
             taxable_interest +
             (dividends - qualified_dividends) -
             ira_deduction -
             student_loan_interest)
    
  2. 遵循数学的传统通常会导致代码更具可读性:

    # 二元操作符之前换行
    income = (gross_wages 
              + taxable_interest 
              + (dividends - qualified_dividends) 
              - ira_deduction 
              - student_loan_interest)
    

注:在Python代码中,只要约定在本地是一致的,就可以在二元操作符之前或者之后换行。

1.5 空行

  • 顶层方法或者类定义使用两个空行;
  • 类中的方法定义使用单个空行;
  • 可以使用额外的空行(尽量少)来分隔一组相关的函数;
  • 在一些列相关的仅占一行的函数之间,空行可以被省略(比如一组虚函数定义);
  • 在方法内部,使用空行来表示逻辑区域(尽量少)。
1.6 源文件编码

  • Python核心发行版中的源代码应该一直使用utf-8编码(Python2中使用ASCII编码)。

  • 使用ASCII编码(Python2)或者utf-8编码(Python3)的文件不应该添加编码声明。

1.7 模块引入

  • 导入通常应该在单独的行上,但是从单个模块中导入多个部分是可以写在一行上:

    # 正确的写法
    import os
    import sys
    
    # 错误的写法
    import os, sys
    
    # 单个模块导入多个部分
    from subprocess import Popen, PIPE
    
  • 导入模块应该放在文件顶部,在模块注释和文档字符串之后,在模块全局变量和常量之前。

  • 导入应按以下顺序分组:

    1. 标准库导入;
    2. 相关的第三方库导入;
    3. 本地库或者方法导入。

    注:在每组导入之间应该放置一个空行。

  • 推荐使用绝对导入,因为如果导入系统配置不正确,则它们通常更具可读性,并且通常表现更好。

    # 绝对导入
    import mypkg.sibling
    from mypkg import sibling
    from mypkg.sibling import example
    
  • 显示相对导入是绝对导入的一种可接受的替代方法,尤其是在处理复杂的包装布局时,绝对导入显得冗余的情况下。

    # 显示相对导入
    from . import sibling
    from .sibling import example
    
  • 隐式相对导入应该禁止使用,在Python3中已经被去掉了。

    # 隐式相对导入
    import sibling
    
  • 从包含类的模块中导入类。

    # 一般情况
    from myclass import MyClass
    from foo.bar.yourclass import YourClass
    
    # 如果和本地名称冲突,则应该明确拼写它们
    import myclass
    import foo.bar.yourclass
    
  • 应该避免使用通配符导入。

    # 通配符导入
    from module import *
    
1.8 模块级双下划线命名

模块级别双下划线变量比如__all____author__应该放在模块文档字符串之后,但是需要放在import语句之前。存在一个例外,from __future__ import应该放在双下划线变量之前,Python要求模块中__future__的导入必须出现在除文档字符串之外的任何其他代码之前。代码如下所示:

# 文档字符串,放在最前面
"""
This is the example module.
This module does stuff.
"""

# 特殊情况,__future__导入必须放在除了文档字符串之后的所有代码前面
from __future__ import barry_as_FLUFL

# 双下划线变量,必须放在import之前
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

# import 语句
import os
import sys

二.字符串引号


Python中表示字符串时,不管是单引号还是双引号都是一样的。但是不推荐将这两种方式看作一样并且混用,最好选择一种规则并坚持使用。当字符串中包含单引号时,采用双引号来表示字符串,反之也一样,这样可以避免使用反斜杠,代码也更易读。

对于三引号标识的字符串,使用双引号字符串来表示,这样可以和PEP 257的文档字符串规则保持一致。

三.表达式和语句中的空格


3.1 需要注意的地方

在下列情形中避免使用过多的空白:

  • 方括号、圆括号和花括号之后。

    # 正确的例子
    spam(ham[1], {eggs: 2})
    
    # 错误的例子
    spam( ham[ 1 ], { eggs: 2 } )
    
  • 逗号、分号和冒号之前。

    # 正确的例子
    if x == 4print x, y; x, y = y, x
    
    # 错误的例子
    if x == 4 : print x , y ; x , y = y , x
    
  • 但是,在切片操作时,冒号和二元运算符是一样的,应该在其左右两边保留相同数量的空格(就像对待优先级最低的运算符一样)。在扩展切片操作中,所有冒号的左右两边空格数都应该相等。不过也有例外,当切片操作中的参数被省略时,应该也忽略空格。

    # 正确的例子
    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]
    
  • 在调用函数时传递参数列表的括号之前。

    # 正确的例子
    spam(1)
    
    # 错误的例子
    spam (1)
    
  • 在索引和切片操作的左括号之前。

    # 正确的例子
    dct["key"] = lst[index]
    
    # 错误的例子
    dct ["key"] = lst [index]
    
  • 赋值(或其他)运算符周围使用多个空格来和其他语句对齐。

    # 正确的例子
    x = 1
    y = 2
    long_variable = 3
    
    # 错误的例子
    x             = 1
    y             = 2
    long_variable = 3
    
3.2 其他建议

  • 避免任何行末的空格。因为它通常是不可见的,它可能会令人困惑:例如反斜杠后跟空格和换行符不会作为续行标记。一些编辑器会自动去除行末空格,许多项目(如CPython本身)都有提交前的预处理钩子来自动去除行末空格。

  • 在二元运算符的两边都使用一个空格:赋值运算符(=),增量赋值运算符(+=-= 等),比较运算符(==<>!=<><=>=innot inisis not),布尔运算符(andornot)。

  • 如果使用了优先级不同的运算符,则在优先级较低的操作符周围增加空白。请你自行判断,不过永远不要用超过一个空格,永远保持二元运算符两侧的空白数量一样。

    # 正确的例子
    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)
    
  • 使用=符号来表示关键字参数或参数默认值时,不要在其周围使用空格。

    # 正确的例子
    def complex(real, imag=0.0):
    return magic(r=real, i=imag)
    
    # 错误的例子
    def complex(real, imag = 0.0):
    return magic(r = real, i = imag)
    
  • 函数注解中的:也遵循一般的:加空格的规则,在->两侧各使用一个空格。(参见[函数注解](####7.1 函数注解))

    # 正确的例子
    def munge(input: AnyStr): ...
    def munge() -> AnyStr: ...
    
    # 错误的例子
    def munge(input:AnyStr): ...
    def munge()->PosInt: ...
    
  • 在组合使用函数注解和参数默认值时,需要在=两侧各使用一个空格(只有当这个参数既有函数注解,又有默认值的时候)。

    # 正确的例子
    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): ...
    
  • 复合语句(即将多行语句写在一行)一般是不鼓励使用的。

    # 正确的例子
    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/for/while中的语句写在一行,但对于有多个分句的语句永远不要这样做。也要避免将多行都写在一起。

    # 最好不要这样
    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()
    

四.何时使用行尾逗号


  • 末尾逗号通常是可选的,除非在定义单元素元组tuple时是必需的(而且在Python 2中,它们具有print语句的语义)。为了清楚起见,建议使用括号(技术上来说是冗余的)括起来。

    # 正确的例子
    FILES = ('setup.cfg',)
    
    # 也正确,但令人困惑
    FILES = 'setup.cfg',
    
  • 当使用版本控制系统时,在将来有可能扩展的列表末尾添加冗余的逗号是有好处的。具体的做法是将每一个元素写在单独的一行,并在行尾添加逗号,右括号单独占一行。但是,与有括号在同一行的末尾元素后面加逗号是没有意义的(上述的单元素元组除外)。

    # 正确的例子
    FILES = [
        'setup.cfg',
        'tox.ini',
    ]
    initialize(
        FILES, 
        error=True,
    )
    
    # 错误的例子
    FILES = ['setup.cfg', 'tox.ini',]
    initialize(FILES, error=True,)
    

五.注释


和代码矛盾的注释还不如没有。当代码有改动时,一定要优先更改注释使其保持最新。

注释应该是完整的多个句子。如果注释是一个短语或一个句子,其首字母应该大写,除非开头是一个以小写字母开头的标识符(永远不要更改标识符的大小写)。

如果注释很短,结束的句号可以被忽略。块注释通常由一段或几段完整的句子组成,每个句子都应该以句号结束。

在多句注释中,除了最后一句之外,你应该在句尾的句号后再加上2个空格。

5.1 块注释

块注释一般写在对应代码之前,并且和对应代码有同样的缩进级别。块注释的每一行都应该以#和一个空格开头(除非该文本是在注释内缩进对齐的)。

块注释中的段落应该用只含有单个#的一行隔开。

5.2 行内注释

尽量少用行内注释。

行内注释是和代码语句写在一行内的注释。行内注释应该至少和代码语句之间有两个空格的间隔,并且以#和一个空格开始。

行内注释通常不是必要的,在代码含义很明显时甚至会让人分心。

请不要这样写:

x = x + 1             # x自加

但是正确的注释会有很大帮助:

x = x + 1             # 边界补偿(注:多用于起点为1的列表,字典等的索引之类的情况)
5.3 文档字符串

要知道如何写出好的文档字符串,请参考PEP 257

  • 所有的公共模块、函数、类和方法都应该有文档字符串。对于非公共方法,文档字符串不是必要的,但你应该留有注释说明该方法的功能,该注释应当出现在def的下一行。

  • PEP 257描述了好的文档字符串应该遵循的规则。其中最重要的是,多行文档字符串以单行"""结尾,不能有其他字符,例如:

    """Return a foobang
    
    Optional plotz says to frobnicate the bizbaz first.
    """
    
  • 对于仅有一行的文档字符串,结尾处的"""应该也写在这一行。

六.命名规范


Python标准库的命名规范有一些混乱,因此我们永远都无法保持一致。但如今仍然存在一些推荐的命名标准。新的模块和包(包括第三方框架)都应该采用这些标准,但已经存在的库如果之前采用其他风格,更推荐保持代码内部的一致性。

6.1 首要原则

对于用户可见的公共部分API,其命名应当表达出功能用途而不是其具体的实现细节。

6.2 描述:命名风格

有很多种不同的命名风格。如果能够认出使用的是什么命名风格将会很有帮助,这和名字被用来做什么是独立的。

通常区分以下命名样式:

  • b (单个小写字母)

  • B (单个大写字母)

  • lowercase(小写字母)

  • lower_case_with_underscores(带下划线的小写字母)

  • UPPERCASE(大写字母)

  • UPPER_CASE_WITH_UNDERSCORES(带下划线的大写字母)

  • CapitalizedWords (也叫做CapWords或者CamelCase– 因为单词首字母大写看起来很像驼峰)

    注意:当CapWords里包含缩写时,将缩写部分的字母都大写。HTTPServerErrorHttpServerError要好。

  • mixedCase (和CapitalizedWords不同在于其首字母小写)

  • Capitalized_Words_With_Underscores (带下划线的驼峰,比较丑)

也有风格使用简短唯一的前缀来表示一组相关的命名。这在Python中并不常见,但为了完整起见这里也捎带提一下。比如,os.stat()函数返回一个tuple,其中的元素名原本为st_mode,st_size,st_mtime等等。(这样做是为了强调和POSIX系统调用结构之间的关系,可以让程序员更熟悉。)

X11库中的公共函数名都以X开头。在Python中这样的风格一般被认为是不必要的,因为属性和方法名之前已经有了对象名的前缀,而函数名前也有了模块名的前缀。

此外,要区别以下划线开始或结尾的特殊形式(可以和其它的规则结合起来):

  • _single_leading_underscore: 以单个下划线开头是“内部使用”的弱标志。 比如,from M import *不会import下划线开头的对象。
  • single_trailing_underscore_:以单个下划线结尾用来避免和Python关键词产生冲突,例如:
Tkinter.Toplevel(master, class_="ClassName")
  • __double_leading_underscore:以双下划线开头的风格命名类属性表示触发命名修饰(在FooBar类中,__boo命名会被修饰成_FooBar__boo)。
  • __double_leading_and_trailing_underscore__:以双下划线开头和结尾的命名风格表示“魔术”对象或属性,存在于用户控制的命名空间(user-controlled namespaces)里(也就是说,这些命名已经存在,但通常需要用户覆写以实现用户所需要的功能)。 比如, __init____import____file__。请依照文档描述来使用这些命名,千万不要自己发明。
6.3 规范:命名规范

6.3.1 需要避免的命名

不要使用符号lL的小写字母),Oo的大写字母),Ii的大写字母)作为单字符的变量名。

在一些字体中,这些字体很难与0 或1进行区分。比如,当尝试使用字符l 时,改成使用L 提醒替代。

6.3.2 ASCII兼容性

标准库中使用的标识符必须与ASCII兼容(参见PEP 3131中的Policy这一节)。

6.3.3 包和模块命名

模块命名应短小,且为全小写,若下划线能提高可读性,也可以在模块名中使用。Python包命名也应该短小,且为全小写,但不应使用下划线。

当使用C/C++写的扩展模块有相应的Python模块提供更高级的接口时(比如,更加面向对象),C/C++模块名应该以下划线开头(例如,_sociket)。

6.3.4 类命名

类命名应该使用驼峰(CapWords)的命名约定。

当接口已有文档说明且主要是被用作调用时,也可以使用函数的命名约定。

注意对于内建命名(builtin names)有一个特殊的约定:大部分内建名都是一个单词(或者两个一起使用的单词),驼峰(CapWords)的约定只对异常命名和内建常量使用。

6.3.5 类型变量命名

PEP 484中引入的类型变量名称通常应使用简短的驼峰命名: TAnyStrNum。建议将后缀_co_contra添加到用于声明相应的协变(covariant)和逆变(contravariant)的行为。例如:

from typing import TypeVar

VT_co = TypeVar('VT_co', covariant=True')
KT_contra = TypeVar('KT_contra', contravariant=True)
6.3.6 异常命名

因为异常是类,类的命名规范可以应用在这里。但是,你应该在异常名上使用Error 后缀(如果这个异常确实是一个Error)。

6.3.7 全局变量命名

(在此之前,我们先假定这些变量都仅在同一个模块内使用。)这些规范同样也适用于函数命名。

对于引用方式设计为from M import *的模块,应该使用__all__机制来避免import全局变量,或者采用下划线前缀的旧规范来命名全局变量,从而表明这些变量是“模块非公开的”。

6.3.8 函数和变量命名

函数命名应该都是小写,必要时使用下划线来提高可读性。

变量命名和函数命名相同的规则。

只有当已有代码风格已经是混合大小写时(比如threading.py),为了保留向后兼容性才使用混合大小写。

6.3.9 函数和方法参数

实例方法的第一参数永远都是self

类方法的第一个参数永远都是cls

在函数参数名和保留关键字冲突时,相对于使用缩写或拼写简化,使用以下划线结尾的命名一般更好。比如,class_class更好。(或许使用同义词避免这样的冲突是更好的方式。)

6.3.10 方法命名和实例变量

使用函数命名的规则:小写单词,必要时使用下划线分开以提高可读性。

仅对于非公开方法和变量命名在开头使用一个下划线。

避免和子类的命名冲突,使用两个下划线开头来触发Python的命名修饰机制。

Python类名的命名修饰规则:如果类Foo有一个属性叫__a,不能使用Foo.__a的方式访问该变量(在类的内部可以通过self.__a的方式去访问,在外部有用户可能仍然坚持使用Foo._Foo__a的方法访问)。一般来说,两个下划线开头的命名方法仅用于避免与设计为子类的类中的属性名冲突。

注意:关于__names的使用也有一些争论。

6.3.11 常量

常量通常是在模块级别定义的,使用全部大写并用下划线将单词分开。如:MAX_OVERFLOWTOTAL

6.3.12 继承的设计

记得永远区别类的方法和实例变量(属性)应该是公开的还是非公开的。如果有疑虑的话,请选择非公开的;因为之后将非公开属性变为公开属性要容易些。

公开属性是那些你希望和你定义的类无关的客户来使用的,并且确保不会出现向后不兼容的问题。非公开属性是那些不希望被第三方使用的部分,你可以不用保证非公开属性不会变化或被删除。

我们在这里没有使用“私有private”这个词,因为在Python里没有什么属性是真正私有的(这样设计省略了大量不必要的工作)。

另一类属性属于子类API的一部分(在其他语言中经常被称为protected)。一些类是为继承设计的,要么扩展要么修改类的部分行为。当设计这样的类时,需要谨慎明确地决定哪些属性是公开的,哪些属于子类API,哪些只会被你的基类调用。

请记住以上几点,下面是Python规范的指南:

  • 公开属性不应该有开头下划线。

  • 如果公开属性的名字和保留关键字有冲突,在你的属性名尾部加上一个下划线。这比采用缩写和简写更好。(然而,和这条规则冲突的是,cls对任何变量和参数来说都是一个更好地拼写,因为大家都知道这表示class,特别是在类方法的第一个参数里。)

    注意:对于类方法,参考之前的函数和方法参数命名建议。

  • 对于简单的公共数据属性,最后仅公开属性名字,不要公开复杂的调用或设值方法。请记住,如果你发现一个简单的数据属性需要增加功能行为时,Python为功能增强提供了一个简单的途径。这种情况下,使用Property注解将功能实现隐藏在简单数据属性访问语法之后。

    注意 1:Property注解仅仅对新规范类有用。

    注意 2:尽量保证功能行为没有副作用,尽管缓存这种副作用看上去并没有什么大问题。

    注意 3:对计算量大的运算避免使用property;属性的注解会让调用者相信访问的运算量是相对较小的。

  • 如果你的类将被子类继承的话,你有一些属性并不想让子类访问,考虑将他们命名为两个下划线开头并且结尾处没有下划线。这样会触发Python命名修饰算法,类名会被修饰添加到属性名中。这样可以避免属性命名冲突,以免子类会不经意间包含相同的命名。

    注意 1:注意命名修饰仅仅是简单地将类名加入到修饰名中,所以如果子类有相同的类名和属性名,你可能仍然会遇到命名冲突问题。

    注意 2:命名修饰可能导致某些用途不太方便,比如调试和__getattr__()。然而命名修饰算法可以更好的被记录,并且易于手动执行。

    注意 3:不是所有人都喜欢命名修饰。需要试着去平衡避免偶然命名冲突的需求和高级调用者使用的潜在可能性。

6.4 公开和内部接口

任何向后兼容性保证仅对公开接口适用。因此,用户能够清楚分辨公开接口和内部接口是很重要的。

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

为了更好审视公开接口和内部接口,模块应该在__all属性中明确声明公开API是哪些。将__all__设为空列表表示该模块中没有公开API。

即使正确设置了__all__属性,内部接口(包,模块,类,函数,属性或其他命名)也应该以单下划线开头。

如果接口的任一一个命名空间(包,模块或类)是内部的,那么该接口也应该是内部的。

导入的命名应该永远被认为是实现细节。其他模块不应当依赖这些非直接访问的导入命名,除非它们在文档中明确地被写为模块的API,例如那些从子模块公开的功能os.path或者包的__init__模块。

七.编程建议


  • 代码应该以不影响其他PythonPyPyJythonIronPythonCythonPsyco等)实现的方式编写。

    例如,不要依赖于 CPython在字符串拼接时的优化实现,形如这种语句形式a += ba = a + b。即使是 CPython(仅对某些类型起作用) 这种优化也是脆弱的,而且在不使用引用计数的实现中根本不存在优化。在库中性能敏感的部分,用''.join形式来代替。这会确保在所有不同的实现中字符串拼接是线性时间的。

  • 与单例作比较,像None应该用isis not,从不使用==操作符。

    另外,当你真的要判断x是不是None时当心if x is not None这样的写法。例如,测试一个默认值为None的变量或参数是否设置成了其它值,其它值有可能是某种特殊类型(如容器),这种特殊类型在逻辑运算时其值会被当作Flase来看待。

  • is not操作符而不是not ... is。虽然这两个表达式是功能相同的,但是前者更易读,是首选。

    # 正确的例子
    if foo is not None
    
    # 错误的例子
    if not foo is None
    
  • 用大量的比较实现排序操作的时候,最好实现所有的比较操作符( __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语句可以提供的唯一好处(即lambda能镶嵌在一个很长的表达式里)。

  • 异常类应派生自Exception而不是BaseException。直接继承BaseException是为Exception保留的,从BaseException继承并捕获异常这种做法几乎总是错的。

    设计异常的层次结构,应基于那些可能出现异常的代码,而不是引发异常的位置。编码的时候,以回答“出了什么问题?”为目标,而不是仅仅指出“这里出现了问题”(见 PEP 3151 一个内建异常结构层次的例子)。

    类的命名规范适用于异常,如果异常类是一个错误,你应该给异常类加一个后缀Error。用于非本地流程控制或者其他形式信号的非错误异常不需要一个特殊的后缀。

  • 适当的使用异常链。在Python 3里,应该使用raise X from Y来指示显式替换,而不会丢失原始的追溯。

    当有意替换一个内部的异常时(在Python 2raise X,Python 3.3+ 用raise X from None),请确保将相关详细信息转移到新异常中(例如,将KeyError转换为AttributeError时保留属性名称,或将原始异常的文本嵌入到新的异常消息中)。

  • 捕获异常时,尽可能使用明确的异常,而不是用一个空的except:语句。例如:

    try:
        import platform_specific_module
    except ImportError:
        platform_specific_module = None
    

    一个空的except:语句将会捕获到SystemExitKeyboardInterrupt异常,很难区分程序的中断到底是Ctrl+C还是其他问题引起的。如果你想捕获程序的所有错误,使用except Exception:(空except:等同于except BaseException)。

    一个好的经验是限制使用空except语句,除了这两种情况:

    1. 如果异常处理程序会打印出或者记录回溯信息;至少用户意识到错误的存在。
    2. 如果代码需要做一些清理工作,但后面用raise向上抛出异常。try ... finally是处理这种情况更好的方式。
  • 绑定异常给一个名字时,最好使用Python 2.6里添加的明确的名字绑定语法:

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

    Python 3只支持这种语法,避免与基于逗号的旧式语法产生二义性。

  • 捕获操作系统异常时,最好使用Python 3.3里引进的明确的异常结构层次,而不是内省的errno值。

  • 另外,对于所有try / except子句,将try子句限制为必需的绝对最小代码量。同样,这样可以避免屏蔽错误。

    # 正确的例子
    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)
    

    第二个例子没有提供任何信息来表明__enter____exit__方法在完成一个事务后做了一些除了关闭连接以外的其它事。在这种情况下明确是很重要的。

  • 坚持使用return语句。函数内的return语句都应该返回一个表达式,或者None。如果一个return语句返回一个表达式,另一个没有返回值的应该用return None清晰的说明,并且在一个函数的结尾应该明确使用一个return语句(如果有返回值的话)。

    # 正确的例子
    
    def foo(x):
        if x >= 0:
            return math.sqrt(x)
        else:
            return None
    
    def bar(x):
        if x < 0:
            return None
        return math.sqrt(x)
    
    # 错误的例子
    
    def foo(x):
        if x >= 0:
            return math.sqrt(x)
    
    def bar(x):
        if x < 0:
            return
        return math.sqrt(x)
    
  • 用字符串方法代替字符串模块。

    字符串方法总是快得多,并且与unicode字符串共享相同的API。如果需要与2.0以下的Python向后兼容,则覆盖此规则。

  • ''.startswith()''.endswith()代替字符串切片来检查前缀和后缀。

    startswith()endswith()是更简洁的,不容易出错的。例如:

    # 正确的例子
    if foo.startswith('bar'):
        
    # 错误的例子
    if foo[:3] == 'bar':
    
  • 对象类型的比较应该始终使用isinstance()而不是直接比较。

    # 正确的例子
    if isinstance(obj, int):
    
    # 错误的例子
    if type(obj) is type(1):
    

    当比较一个对象是不是字符串时,记住它有可能也是一个unicode字符串!在Python 2里面,strunicode有一个公共的基类叫basestring,因此你可以这样做:

    if isinstance(obj, basestring):
    

    注意:在Python 3里面,unicodebasestring已经不存在了(只有str),byte对象不再是字符串的一种(被一个整数序列替代)。

  • 对于序列(字符串、列表、元组)来说,空的序列为False

    # 正确的例子
    if not seq:
    if seq:
        
    # 错误的例子
    if len(seq):
    if not len(seq):
    
  • 不要让字符串对尾随的空格有依赖。这样的尾随空格是视觉上无法区分的,一些编辑器(或者近期的,reindent.py)会将其裁剪掉。

  • 不要用==比较TrueFalse

    # 正确的例子
    if greeting:
        
    # 错误的例子
    if greeting == True:
        
    # 更糟糕的例子
    if greeting is True:
    
  • 不鼓励在try/finallyfinally套件中使用流控制语句return/break/continue,因为在该方法中流控制语句将跳到finally套件之外,会隐式取消通过finally套件传播的任何活动异常。

    # 错误的例子
    
    def foo():
        try:
            1 / 0
        finally:
            return 42
    
7.1 函数注解

随着PEP 484被正式接受,函数注释的样式规则已经改变。

  • 为了向前兼容,Python 3代码中的函数注释最好使用PEP 484语法。

  • 建议不再使用在此文档早期版本中描述的试验性质的注解样式。

  • 然而,在标准库stdlib之外,现在鼓励在PEP 484的规则范围内的实验。例如,使用PEP 484样式类型的注解标记大型第三方库或应用程序,评估添加这些注解的方式是否简便,并观察其存在是否增加了代码可读性。

  • Python标准库在采用这些注解时应持谨慎态度,但是当编写新代码或进行大的重构时,允许使用。

  • 如果希望不按照函数注解的方式来使用函数,可以在文件头部添加以下注释:

    # type: ignore
    

    这会告诉类型检查器忽略所有注解。(在PEP 484中可以找到更细致的方式来控制类型检查器的行为。)

  • 像代码扫描工具一样linters,类型检查器是可选的、单独的工具。默认情况下,Python解释器不应该由于类型检查而发出任何消息,并且不应该根据注释来改变它们的行为。

  • 不想使用类型检查器的用户可以忽略它们。但是,预计第三方库软件包的用户可能希望在这些软件包上运行类型检查器。为此,PEP 484 建议使用存根文件:.pyi文件,类型检查器优先于相应的.py文件读取这个文件。存根文件可以与库一起分发,也可以单独地(在库作者许可的情况下)通过Typeshed repo分发。

  • 对于需要向后兼容的代码,可以以注释的形式添加类型注解。参见PEP 484的相关章节

7.2 变量注解

PEP 526 引入了变量注解。对于变量注解的推荐样式类似于上面的函数注解:

  • 对于模块层的变量,类或实例变量,和局部变量的注解应该在冒号之后有一个空格。
  • 在冒号之前应该没有空格。
  • 如果赋值有右侧的值,那么等号两侧都应该有一个空格。
# 正确的例子

code: int

class Point:
    coords: Tuple[int, int]
    label: str = '<unknown'
        
# 错误的例子

code:int  # No space after colon
code : int  # Space before colon

class Test:
    result: int=0  # No spaces around equality sign
  • 尽管PEP 526 已经被Python 3.6接受,但变量注解语法是所有Python版本中存根文件的首选语法(有关详细信息,请参阅PEP 484)。

参考链接

  1. Python PEP8 编码风格指南
  2. PEP8 规范 - 易迟的博客
  3. PEP 8 – Style Guide for Python Code
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值