Python 编程规范

转自https://zhuanlan.zhihu.com/p/702181903

Python 规范主要指的是编写 Python 代码时应遵循的一系列编程风格指南,其中最著名和广泛接受的是PEP 8Python Enhancement Proposal 8)。PEP 8 是由 Python 社区制定的官方风格指南,旨在提升代码的可读性和一致性。

规范要点

代码布局

使用4个空格进行缩进,而不是制表符。

每行代码长度建议不超过79字符,若超过则适当换行。

在顶级定义之间空两行,在方法定义之间空一行。

当行过长需要续接到下一行时,可以在运算符后换行,并在新行进行适当缩进。

命名规则

变量名、函数名应使用小写字母和下划线(snake_case)。

类名使用驼峰命名法(CapWords或CamelCase)。

常量全大写,单词间用下划线分隔。

导入语句

导入语句应放在文件开头,先标准库导入,后第三方库导入,最后是本地应用/模块导入。

每个导入语句应单独一行,可以使用括号来分组多条导入语句。

字符串引号

优先使用单引号(' '),除非字符串中包含单引号,此时使用双引号(" ")。

多行字符串使用三引号('''或""")。

空格使用

在操作符两边各加一个空格:a = b + c。

不要在逗号、冒号或分号前后加空格,如my_list = [1, 2, 3]。

函数参数间的逗号后加空格,如def my_function(a, b):。

注释

使用井号(#)来标记单行注释,确保注释简洁明了。

对于模块、类、函数等,使用文档字符串(docstrings)进行注释,遵循PEP 257规范。

异常处理

使用try...except...finally结构处理异常,尽量捕获具体异常而非使用泛型的except。

类与实例方法

实例方法的第一个参数应为self,类方法的第一个参数为cls。

类的属性和方法应避免使用下划线作为前缀或后缀(除非有特殊含义,如私有属性)。

迭代

遍历序列时,优先使用for...in循环而非索引访问。

规范详解

代码布局

缩进

每个缩进级别使用4个空格

行延续时,应该括号、方括号、花括号在内的隐式行折行元素垂直对齐,或者使用悬挂缩进(悬挂缩进是一种排版样式,其中段落中除第一行外的所有行都要缩进。在Python上下文中,带括号语句的开始括号是该行的最后一个非空白字符,随后的行被缩进直到结束括号)。

当使用悬挂缩进时,需要注意以下几点:

第一行不应包含任何参数,并且应该通过进一步的缩进来明确区分其为续行。

4个空格规则是可选的

# 正确示例:# 与开始分割符对齐.foo=long_function_name(var_one,var_two,var_three,var_four)# 增加4个空格 (额外的一层缩进) 以区分参数.deflong_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)# 悬挂缩进 4个空格规则 是可选的.foo=long_function_name(var_one,var_two,var_three,var_four)# 错误示例:# 非垂直对齐时,第一行有参数.foo=long_function_name(var_one,var_two,var_three,var_four)# 参数没有进一步缩进.deflong_function_name(var_one,var_two,var_three,var_four):print(var_one)

当if语句的条件部分足够长,需要写在多行时,值得注意的是,“if” 这个两个字符的关键字加上一个空格和一个括号,为后续的多行条件创建了一个自然的4个空格的缩进。会与嵌套在if语句中的代码产生视觉冲突。如何或是否进一步在视觉上区分这样的条件行与嵌套在if语句中的代码。在这种情况下,没有明确的规范,可接受的选项包括但不限于:

没有额外的缩进

添加一些注释,可以提供在编译器上的一些区分;支持语法高亮

在条件续行上,添加一些额外的缩进

# 没有额外的缩进

if (this_is_one_thing and

    that_is_another_thing):

    do_something()

# 添加一些注释,可以提供在编译器上的一些区分,如支持语法高亮

if (this_is_one_thing and

    that_is_another_thing):

    # Since both conditions are true, we can frobnicate.

    do_something()

# 在条件续行上,添加一些额外的缩进

if (this_is_one_thing

        and that_is_another_thing):

    do_something()

在多行结构上的右括号、方括号、花括号可以排在列表最后一行的第一个非空白字符下,也可以排列在多行构造的行的第一个字符下,如下所示:

# 列表最后一行的第一个非空白字符下

my_list = [

    1, 2, 3,

    4, 5, 6,

    ]

result = some_function_that_takes_arguments(

    'a', 'b', 'c',

    'd', 'e', 'f',

    )

# 多行构造的行的第一个字符下

my_list = [

    1, 2, 3,

    4, 5, 6,

]

result = some_function_that_takes_arguments(

    'a', 'b', 'c',

    'd', 'e', 'f',

)

制表符还是空格?

空格是首选的缩进方法。

制表符应仅用于与已使用制表符缩进的代码保持一致。

Python不允许混合制表符和空格进行缩进。

行最大长度限制

将所有行限制为最多79个字符。对于结构限制较少的长文本块(文档字符串或注释),行长应限制为72个字符。

限制行长后,当使用代码审查工具在相邻列中显示两个版本的代码,编辑器窗口宽度可以让几个文件并排打开,且显示效果很好。

大多数工具中的默认换行会破坏代码的视觉结构,使其更难以理解。行长限制是为了避免在窗口宽度设置为80的编辑器中换行,即使该工具在最后放置了一个标记符号,一些基于web的工具可能根本不提供动态换行。

一些团队非常喜欢更长的行。对于专门或主要由能够就此问题达成一致的团队维护的代码,可以将行长限制增加到99个字符,前提是注释和文档字符串仍然以72个字符。

Python标准库是保守的,要求将行限制为79个字符(文档字符串/注释限制为72个字符)。

换行的首选方法是在括号、方括号和花括号内使用Python隐式行延续。将表达式括在括号中,可以将长行分隔成多行。应该优先于使用反斜杠进行行延续。

反斜杠有时可能仍然合适。例如,在Python 3.10之前长的,多行的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())

应该在二元运算符之前还是之后换行?

几十年来,推荐的风格是在二进制运算符之后中断。但这可能会在两个方面损害易读性:运算符往往分散在屏幕上的不同列中,每个运算符都被从其操作数移开,移到前一行。在这里,眼睛必须做额外的工作来判断哪些项目被添加,哪些项目被减去:

几十年来,推荐的风格是在二进制运算符之后中断。但是这会从两个方面损害可读性:

运算符往往分散在屏幕上的不同列中,并且每个运算符都从其操作数移到前一行。

眼睛必须做额外的工作来分辨哪些是加,哪些是减

# Wrong

# 运算符离他们的操作数很远

income = (gross_wages +

          taxable_interest +

          (dividends - qualified_dividends) -

          ira_deduction -

          student_loan_interest)

为了解决这个易读性问题,数学家和他们的出版商遵循相反的惯例。Donald Knuth在他的计算机和排版系列中解释了传统的规则:“虽然段落中的公式总是在二进制运算和关系之后中断,但显示的公式总是在二进制运算之前中断“。

遵循数学传统通常会使代码更具可读性:

# Correct:

# 很容易将操作符与操作数匹配

income = (gross_wages

          + taxable_interest

          + (dividends - qualified_dividends)

          - ira_deduction

          - student_loan_interest)

空白行

用两个空行包围顶级函数和类定义。

类内的方法定义被一个空行包围(前一个空行,后一个空行)。

可以(有节制地)使用额外的空行来分隔一组相关的函数。在一堆相关的单行代码(例如,一组虚拟实现)之间可以省略空白行。

在函数中尽量使用空行来表示逻辑部分。

Python接受control-L(即^L)换行符作为空白字符;许多工具将这些字符视为页面分隔符,因此可以使用它们来分隔文件中相关部分的页面。注意,一些编辑器和基于web的代码查看器可能无法将control-L识别为换行符,因此会在其位置上显示另一个字形。

源文件编码

核心Python发行版中的代码应始终使用UTF-8,并且不应具有编码声明。

在标准库中,非UTF-8编码应仅用于测试目的。尽量避免使用非ASCII字符,最好仅用于表示地点和人名。如果使用非ASCII字符作为数据,请避免使用嘈杂的Unicode字符。

Python标准库中的所有标识符都必须使用仅限ASCII的标识符,并且应该在可行的情况下使用英文单词(在许多情况下,使用非英文的缩写和技术术语)。

imports导入

导入通常应在单独的行上

导入总是放在文件的顶部,仅在模块注释和文档字符串之后,在模块全局变量和常量之前。

导入应按以下顺序分组:

标准库。

相关第三方库。

本地应用/库。

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

建议使用绝对导入;如果导入系统配置不正确(例如包中的目录最终位于sys.path上),它们通常更具可读性,而且往往表现得更好(或者至少给出更好的错误消息)。然而,显式相对导入是绝对导入的一种可接受的替代方案,特别是在处理复杂的包布局时,使用绝对导入会有不必要地冗长。标准库应避免使用复杂的包布局,并始终使用绝对导入。

应该避免通配符导入(from import *),它们会使名称空间中出现的名称变得不清楚,从而混淆读者和许多自动化工具。通配符导入有一个合理的用例,它是将内部接口作为公共API的一部分重新发布(例如,用可选加速器模块的定义覆盖接口的纯Python实现,并且确切地说,哪些定义将被覆盖是未知的)

# Correct:

import os

import sys

# Wrong:

import sys, os

# Correct:

from subprocess import Popen, PIPE

# 绝对导入

import mypkg.sibling

from mypkg import sibling

from mypkg.sibling import example

# 相对导入

from . import sibling

from .sibling import example

# 从包含类的模块导入类时,没有命名冲突时,可以这样拼写

from myclass import MyClass

from foo.bar.yourclass import YourClass

# 从包含类的模块导入类时,命名冲突时,可以这样拼写

import myclass

import foo.bar.yourclass

模块级"Dunder"名称

模块级“dunders”(具有两个前导和两个尾随的名称下划线)如__all__、__author__、__version__等,应放在模块文档字符串之后,但在除 from __future__ imports 之外的任何导入语句之前。Python强制要求 future-imports 必须出现在模块中除文档字符串之外的任何其他代码之前

"""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

字符串引号

在Python中,单引号字符串和双引号字符串是一样的。这个PEP对此不做推荐。选择一个规则并坚持下去。但是,当一个字符串包含单引号或双引号字符时,请使用另一个字符以避免字符串中出现反斜杠。它提高了可读性。

表达式和语句中的空格

在以下情况下避免无关的空格

紧接在括号、括号或大括号内

尾随的逗号和后面的右括号之间

紧接在逗号、分号或冒号之前的

在切片中,冒号的作用类似于二进制操作符,并且在两边的应该相等(将其视为具有最低优先级的操作符)。在扩展切片中,两个冒号必须具有相同的间距。例外:省略切片参数时,省略空格

紧接在开始函数调用的参数列表的左圆括号之前

紧接在开始索引或切片的左括号之前

赋值(或其他)操作符周围有多个空格,以便与另一个操作符对齐

# 紧接在括号、括号或大括号内

# Correct:

spam(ham[1], {eggs: 2})

# Wrong:

spam( ham[ 1 ], { eggs: 2 } )

# 尾随的逗号和后面的右括号之间

# Correct:

foo = (0,)

# Wrong:

bar = (0, )

# 紧接在逗号、分号或冒号之前的

# Correct:

if x == 4: print(x, y); x, y = y, x

# Wrong:

if x == 4 : print(x , y) ; x , y = y , x

# 切片中

# Correct:

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]

# Wrong:

ham[lower + offset:upper + offset]

ham[1: 9], ham[1 :9], ham[1:9 :3]

ham[lower : : step]

ham[ : upper]

# 函数调用

# Correct:

spam(1)

# Wrong:

spam (1)

# 开始索引或切片的左括号之前

# Correct:

dct['key'] = lst[index]

# Wrong:

dct ['key'] = lst [index]

# 赋值(或其他)操作符周围有多个空格

# Correct:

x = 1

y = 2

long_variable = 3

# Wrong:

x            = 1

y            = 2

long_variable = 3

其他建议

避免在任何地方留下空格。因为它通常是不可见的,它可能会令人困惑:例如,反斜杠后面跟着空格和换行符不算作行继续标记。一些编辑器不保留它,许多项目(如CPython本身)有拒绝它的预提交钩子。

总是在这些二元操作符的两边分别用一个空格括起来:赋值(=)、增广赋值(+=、-=等)、比较(==、<、>、!=、<>、<=、>=、in、not in、is、is not)、布尔值(and、or、not)。

如果使用具有不同优先级的操作符,请考虑在具有最低优先级的操作符周围添加空白。用你自己的判断;但是,永远不要使用多个空格,并且在二进制操作符的两侧始终具有相同数量的空白。

# Correct:

i = i + 1

submitted += 1

x = x*2 - 1

hypot2 = x*x + y*y

c = (a+b) * (a-b)

# Wrong:

i=i+1

submitted +=1

x = x * 2 - 1

hypot2 = x * x + y * y

c = (a + b) * (a - b)

函数注释应该使用冒号的常规规则,并且如果存在->箭头周围总是有空格。

# Correct:

def munge(input: AnyStr): ...

def munge() -> PosInt: ...

# Wrong:

def munge(input:AnyStr): ...

def munge()->PosInt: ...

当用于指示关键字参数时,或者用于指示未标注的函数形参的默认值时,不要在=号周围使用空格

# Correct:

def complex(real, imag=0.0):

    return magic(r=real, i=imag)

# Wrong:

def complex(real, imag = 0.0):

    return magic(r = real, i = imag)

# 当将参数注释与默认值结合使用时,一定要在 = 号周围使用空格

# Correct:

def munge(sep: AnyStr = None): ...

def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...

# Wrong:

def munge(input: AnyStr=None): ...

def munge(input: AnyStr, limit = 1000): ...

虽然有时将if/for/ While语句与一个小体放在同一行是可以的,但对于多子句语句永远不要这样做。也不要折叠这么长的线。

# Wrong:

if foo == 'blah': do_blah_thing()

for x in lst: total += x

while t < 10: t = delay()

# Wrong:

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()

何时使用尾随逗号

尾随逗号通常是可选的,除非在制作一个元素的元组时它们是强制性的。为清楚起见,建议将后者括在(技术上多余的)括号中

# Correct:

FILES = ('setup.cfg',)

# Wrong:

FILES = 'setup.cfg',

当尾随逗号是多余的,当使用版本控制系统时,当期望值、参数或导入项的列表随着时间的推移而扩展时,它们通常是有用的。该模式是将每个值(等)单独放在一行上,总是在后面添加逗号,并在下一行添加右括号/大括号/大括号。然而,在结束分隔符的同一行上有一个尾随逗号是没有意义的(除了上面的单例元组)。

# Correct:

FILES = [

    'setup.cfg',

    'tox.ini',

    ]

initialize(FILES,

          error=True,

          )

# Wrong:

FILES = ['setup.cfg', 'tox.ini',]

initialize(FILES, error=True,)

注释

与代码相矛盾的注释比没有注释更糟糕。当代码更改时,始终优先考虑保持更新注释!

注释应该是完整的句子。第一个单词应该大写,除非它是以小写字母开头的标识符(永远不要改变标识符的大小写!)

块注释通常由一个或多个由完整句子组成的段落组成,每个句子以句号结尾。

在多句注释中,除了最后一句之外,你应该在句子结束后使用一到两个空格

确保你的评论是清晰的,很容易被其他的,你写作的使用语言的人理解。

来自非英语国家的Python程序员:请用英语写你的注释,除非你120%确定代码永远不会被不讲你的语言的人阅读。

块注释

块注释通常适用于部分(或全部)代码之后,并缩进到与该代码相同的级别。每一行块注释以#和单个空格开头(除非是评论中的缩进文本)。

块注释中的段落由包含单个#的行分隔。

行内注释

谨慎使用行内注释。

行内注释是与语句在同一行上的注释。行内注释应与语句至少用两个空格分隔。它们应该以#和单个空格开头。

行内注释是不必要的,如果它们陈述了显而易见的内容,实际上会分散注意力。不要这样做:

x = x + 1                # Increment x,不要这样做

x = x + 1                # Compensate for border, 这种情况下是有用的

文档字符串

编写好的文档字符串(又名“docstrings”)的约定在PEP 257中得到了不朽的体现。

为所有公共模块、函数、类和方法编写文档字符串。文档字符串对于非公共方法不是必需的,但应该有一个注释来描述该方法的功能。这个注释应该出现在 def 之后。

PEP 257描述了良好的文档字符串约定。注意,最重要的是,多行文档字符串结尾的"""应该单独在一行上

"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.

"""

对于单行文档字符串,请将结束的"""保持在同一行

"""Return an ex-parrot."""

命名约定

Python库的命名约定有点混乱,所以我们永远不会完全一致——尽管如此,这里是目前推荐的命名标准。新的模块和包(包括第三方框架)应该按照这些标准编写,但是如果现有库有不同的风格,内部一致性是首选。

最重要的原则

作为API的公共部分对用户可见的名称应遵循反映用法而不是实现的约定。

描述性:命名风格

有许多不同的命名风格。它有助于识别正在使用的命名风格,而不是它们的用途。

通常区分以下命名风格:

b(单小写字母)

B(单大写字母)

lowercase

lower_case_with_underscores

UPPERCASE

UPPER_CASE_WITH_UNDERSCORES

CapitalizedWords(或CapWords,或CamelCase——因其字母凹凸不平而得名)。这有时也被称为StudlyCaps,首字母大写

注意:在CapWords中使用首字母缩略词时,请将首字母缩略词的所有字母大写。因此HTTPServerError优于HttpServerError。

mixedCase(与CapitalizedWords不同的是初始小写字符)

Capitalized_Words_With_Underscores(不推荐)

还有一种风格是使用一个简短的唯一前缀将相关的名称组合在一起。这在Python中使用得并不多,但为了完整性而提到它。例如,os.stat()函数返回一个元组,其项通常具有st_mode、st_size、st_mtime等名称。(这样做是为了强调与POSIX系统调用结构的字段的对应关系,这有助于程序员熟悉它。)

X11库的所有公共函数都使用前导X。在Python中,这种风格通常被认为是不必要的,因为属性和方法名的前缀是对象,函数名的前缀是模块名

此外,还可以识别使用前导或尾随下划线的特殊形式:

_single_leading_underscore弱的“内部使用”指示。例如,from M import *不导入名称以下划线开头的对象。

single_trailing_underscore_:按照约定使用,以避免与Python关键字冲突,例如:

tkinter.Toplevel(master, class_='ClassName')

__double_leading_underscore:命名类属性时,调用名称修饰(类内FooBar,__boo变成_FooBar__boo)。

__double_leading_and_trailing_underscore__:存在于用户控制的命名空间中的“魔法”对象或属性。例如__init__, __import__或__file__。永远不要编造这样的名字;只按照文档的要求使用它们

规范性:命名约定

要避免的名称

不要使用字符“l”(小写字母el)、“O”(大写字母oh)或“I”(大写字母eye)作为单字符变量名称。

在某些字体中,这些字符与数字1和0无法区分。当想使用“l”时,请改用“L”。

ASCII兼容性

标准库中使用的标识符必须与ASCII兼容,如PEP 3131的策略部分所述

包和模块名称

模块应该有简短,全小写名称。如果可以提高易读性,可以在模块名称中使用下划线Python包也应该有简短的全小写名称,尽管不鼓励使用下划线。

用C或C++编写的扩展模块提供更高级别(例如更面向对象)的Python模块界面,C/C++模块有一个前导下划线(例如_socket)。

类名称

类名通常应使用CapWords约定。

在接口被记录,并主要用作可调用对象的情况下,可以使用函数的命名约定。

请注意,内置名称有一个单独的约定:大多数内置名称是单个单词(或两个单词一起运行),CapWords约定仅用于异常名称和内置常量。

类型变量名

PEP 484中引入的类型变量的名称通常应该使用CapWords,更倾向于短名称:T、AnyStr、Num。建议添加用于声明协变变量的变量的后缀_co或_contra或相应的逆变行为:

from typing import TypeVar

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

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

异常名称

异常应该是类,所以这里适用类命名约定。但应该在异常名称上使用后缀“Error”(如果异常实际上是错误)。

全局变量名

(希望这些变量仅用于一个模块内。)约定与函数的约定大致相同。

设计为通过from M import * 使用的模块应该使用__all__机制来防止导出全局变量,或者使用较旧的约定,即用下划线为这些全局变量前缀(你可能想要表明这些全局变量是“module non-public”)。

函数和变量名

函数名应该是小写的,必要时用下划线分隔,以提高可读性。

变量名遵循与函数名相同的约定。

mixedCase只允许在已经是流行风格的上下文中使用(例如threading.py),以保持向后兼容性。

函数和方法参数

始终使用self作为实例方法的第一个参数。

始终使用cls作为类方法(通过@classmethod 装饰器标记的方法)的第一个参数。

如果函数参数的名称与保留关键字冲突,通常最好在后面附加一个下划线,而不是使用缩写或拼写错误。因此class_比class好。(也许最好是使用同义词来避免这种冲突。)

方法名称和实例变量

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

仅对非公共方法和实例变量使用一个前导下划线。

为避免与子类发生名称冲突,请使用两个前导下划线来调用Python的名称混淆规则。

Python用类名混淆了这些名称:如果类Foo有一个名为__a的属性,它不能被Foo.__a访问。(固执用户仍然可以通过调用Foo._Foo__a来获得访问权。)通常,双前导下划线应该仅用于避免与设计为子类化的类中的属性发生名称冲突。

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

常量

常量通常在模块级别定义,并以大写字母书写,并用下划线分隔单词。例如MAX_OVERFLOW和TOTAL。

继承设计

始终决定一个类的方法和实例变量(统称为“属性”)应该是公共的还是非公共的。如果有疑问,请选择非公共;稍后将其公开比将公共属性非公开更容易。

公共属性是希望类中不相关的客户端使用的属性,并承诺避免向后不兼容的更改。非公共属性是那些不打算被第三方使用的属性;不能保证非公共属性不会更改甚至被删除。

我们在这里没有使用术语“私有”,因为在Python中没有属性是真正私有的(通常没有不必要的工作量)。

另一类属性是“子类API”的一部分(在其他语言中通常称为“protected”)。有些类被设计为可以继承,以扩展或修改类行为的某些方面。在设计这样的类时,要注意明确地决定哪些属性是公共的,哪些属性是子类API的一部分,哪些属性只能由基类使用。

以下是python继承设计指南:

公共属性不应有前导下划线。

如果公共属性名与保留关键字冲突,请在属性名后面添加一个下划线。这比缩写或错误的拼写更可取。(然而,尽管有此规则,' cls '是已知为类的任何变量或参数的首选拼写,特别是类方法的第一个参数。)

对于简单的公共数据属性,最好只公开属性名称,不使用复杂的访问器/mutator方法。请记住,如果您发现一个简单的数据属性需要发展函数行为,那么Python为将来的增强提供了一条简单的途径。在这种情况下,使用属性将功能实现隐藏在简单的数据属性访问语法之后

如果您的类打算被子类化,并且您有不希望子类使用的属性,请考虑使用双下划线前导而不带下划线来命名它们。这调用了Python的名称混淆算法,其中类的名称被混淆为属性名称。如果子类无意中包含具有相同名称的属性,这有助于避免属性名称冲突

公共和内部接口

任何向后兼容性保证只适用于公共接口。因此,重要的是用户能够清楚地区分公共接口和内部接口。

文档化的接口被认为是公共的,除非文档显式地声明它们是临时的或免除通常的向后兼容性保证的内部接口。所有未归档的接口都应该假定是内部的。

为了更好地支持自省,模块应该使用__all__属性在其公共API中显式声明名称。将__all__设置为空列表表明该模块没有公共API

即使正确设置了__all__,内部接口(包、模块、类、函数、属性或其他名称)仍应以单个下划线作为前缀。

如果任何包含名称空间(包、模块或类)被认为是内部的,那么接口也被认为是内部的。

导入的名称应始终被视为实现细节。其他模块不能依赖于对这些导入名称的间接访问,除非它们是包含模块API(如os)的显式文档部分。路径或包的__init__模块,该模块公开子模块的功能。

编程建议

代码的编写方式不应损害Python的其他实现(PyPy、Jython、IronPython、Cython、Psyco等)。

例如,对于形式为a += b或a = a + b的语句,不要依赖CPython,对就地字符串连接的有效实现。即使在CPython中,这种优化也很脆弱(它仅适用于某些类型),并且在不使用refcounting的实现中根本不存在。在库中对性能敏感的部分,应该使用" .join()形式。这将确保跨各种实现的连接在线性时间内发生。

与None之类的单例比较应该总是使用is或is not,而不是相等操作符。

另外,当你真正想表达 if x is not None 时,要注意 if x 这种表达式,例如,在测试一个变量或参数是默认None 还是被设置为其他值。其他值可能有一个类型(如容器),那么这个bool表达式可能就是 false。

使用是not 操作符而不是not…is。虽然这两个表达式在功能上是相同的,但前者更具可读性,是首选

# Correct:

if foo is not None:

# Wrong:

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 == y和x != y的参数。sort()和min()操作保证使用<操作符,而max()函数使用>操作符。但是,最好实现所有六个操作,这样在其他上下文中就不会出现混淆。

始终使用def语句,而不是将lambda表达式直接绑定到标识符的赋值语句

# Correct:

def f(x): return 2*x

# Wrong:

f = lambda x: 2*x

第一种形式意味着结果函数对象的名称特别为' f '而不是通用的' <lambda> '。一般来说,这对于回溯和字符串表示更有用。赋值语句的使用消除了lambda表达式相对于显式def语句所能提供的唯一好处(即它可以嵌入到更大的表达式中)。

异常类从Exception,而不是BaseException继承。从BaseException直接继承是为了捕获哪些几乎总是错误的异常保留的

基于代码捕获异常区别来设计异常层次结构,可能是必须的,而不是基于引发异常的位置。目的是回答这个问题“哪里出了问题?”,而不是仅仅指出“发生了问题”(参见PEP 3151,获取有关内建异常层次结构的示例)。

这里适用于类命名约定,但如果异常是错误,则应该在异常类后面加上后缀“Error”。用于非本地流程控制或其他形式的信令非错误异常不需要特殊后缀。

适当地使用异常链。应该使用raise X from Y来指示显式替换,而不是丢失原始的回溯。

当故意替换内部异常时(使用raise X from None),确保相关细节被转移到新异常(例如在将KeyError转换为AttributeError时保留属性名称,或在新异常消息中嵌入原始异常的文本)。

当捕获异常时,尽可能地提及特定的异常,而不是仅仅使用except:子句

try:

    import platform_specific_module

except ImportError:

    platform_specific_module = None

仅使用except:子句将捕获SystemExit和KeyboardInterrupt异常,使得用Control-C中断程序变得更加困难,并且可以掩盖其他问题。如果你想捕获程序所有的异常,使用except Exception(仅使用 except等价于 except BaseException)。

一个好的经验法则是将 “except” 子句的使用,限制在以下两种情况:

如果异常处理程序将打印或记录回溯;至少用户会意识到发生了错误。

如果代码需要做一些清理工作,但随后让异常通过raise向上传播。try...finally可以更好地处理这种情况。

在捕获操作系统错误时,请选择Python 3.3中引入的显式异常层次结构,而不是自省errno值。

对于所有try/except子句,需要将 try 语句的代码量限制到最小,这样能避免掩盖bug。

# Correct:

try:

    value = collection[key]

except KeyError:

    return key_not_found(key)

else:

    return handle_value(value)

# Wrong:

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语句也是可以接受的。

上下文管理器在执行除获取和释放资源以外的其他操作时,应该通过单独的函数或方法调用它们。

# Correct:

with conn.begin_transaction():

    do_stuff_in_transaction(conn)

# Wrong:

with conn:

    do_stuff_in_transaction(conn)

后一个例子没有提供任何信息来表明__enter__和__exit__方法,除了在事务结束后关闭连接之外正在做其他事情。在这种情况下,明确是很重要的。

在返回语句中保持一致。函数中的所有返回语句都应该返回一个表达式,或者都不应该返回。如果任何return语句返回一个表达式,任何没有返回值的return语句都应该显式地将其声明为return None,并且应该在函数末尾显式的加上return语句。

# Correct:

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)

# Wrong:

def foo(x):

    if x >= 0:

        return math.sqrt(x)

def bar(x):

    if x < 0:

        return

    return math.sqrt(x)

Use ''.startswith() and ''.endswith() 代替分片检查前缀或后缀

startswith()和endswith()更干净,更不容易出错:

# Correct:

if foo.startswith('bar'):

# Wrong:

if foo[:3] == 'bar':

对象类型比较应该始终使用isinstance(),而不是直接比较类型

# Correct:

if isinstance(obj, int):

# Wrong:

if type(obj) is type(1):

对于序列(字符串、列表、元组),使用空序列为假的事实

# Correct:

if not seq:

if seq:

# Wrong:

if len(seq):

if not len(seq):

不要编写依赖重要末尾空白的字符串文字。这样的尾随空格在视觉上是无法区分的,一些编辑器(或者最近的reindent.py)会修剪它们

不要使用 == 将布尔值 与True或False进行比较

# Correct:

if greeting:

# Wrong:

if greeting == True:

# Worse 更糟

if greeting is True:

使用流控制语句,return/break/continue 应在try…finally内,不鼓励流控制语句跳转到finally之外。这是因为这样的语句将隐式地取消了任何活动在finally内部的异常。

# Wrong:

def foo():

    try:

        1 / 0

    finally:

        return 42

函数注释

随着PEP 484的接受,函数注释的样式也发生了改变。

函数注释应使用PEP 484语法

不再鼓励使用本PEP中先前推荐的注释样式进行实验。

然而,在stdlib之外,现在鼓励在PEP 484规则范围内进行实验。例如,用PEP 484风格的类型注释标记大型第三方库或应用程序,检查添加这些注释的容易程度,并观察它们的存在是否增加了代码的可理解性。

Python标准库在采用这种注释方面应该是保守的,但是对于新代码和大型重构,它们的使用是允许的。。

对于想要以不同的方式使用函数注释的代码,建议使用这种形式的注释。

# type: ignore

靠近文件顶部的;这告诉类型检查器忽略所有注释。(可以在PEP 484中找到禁用类型检查器投诉的更细粒度的方法

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

不想使用类型检查器的可以自由地忽略它们。然而,第三方库包可能希望在这些包上运行类型检查器。为此,PEP 484建议使用由类型检查器读取的存根文件:.pyi文件,而不是相应的.py文件。存根文件可以与库一起发布,也可以通过typeshed repo单独发布(在库作者允许的情况下)。

变量注释

PEP 526引入了变量标注。它们的样式建议类似于上面描述的函数注释:

模块级变量、类和实例变量以及局部变量的注释应该在冒号后面有一个空格。

冒号前不应该有空格。

如果赋值项有右手边,那么等式两边应该正好有一个空格

# Correct:

code: int

class Point:

    coords: Tuple[int, int]

    label: str = '<unknown>'

# Wrong:

code:int  # No space after colon

code : int  # Space before colon

class Test:

    result: int=0  # No spaces around equality sign

尽管Python 3.6接受PEP 526,但变量注释语法是所有Python版本上存根文件的首选语法(详细信息请参阅PEP 484)。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容