器→工具, 编程语言

PEP 8:Python编码规范

钱魏Way · · 125 次浏览

什么是PEP 8

PEP 8 于 2001 年由 Python 创始人Guido van Rossum 和 Barry Warsaw , Nick Coghlan编写而成,是提高代码可读性同时保持项目内代码风格一致的编程指导。PEP 代表 Python Enhancement Proposal,意为 Python 改善提案。PEP是面向开发者,对某个Python功能解释说明设计准则或者功能的辅助性文档。

原始链接:

PEP 8 中文翻译

介绍

这篇文档说明了Python主要发行版中标准库代码所遵守的规范。对于Python的C语言实现中的编码规范,请参考实现Python的C代码风格指南PEP 7。这篇文档和PEP 257(Docstring约定)都改编自Guido1最早的Python风格指南文章,并且添加了一些Barry的风格指南2中的内容。语言自身在发生着改变,随着新的规范的出现和旧规范的过时,代码风格也会随着时间演变。很多项目都有自己的一套风格指南。若和本指南有任何冲突,应该优先考虑其项目相关的那套指南。

保持盲目的一致是头脑简单的表现

Guido的一个重要观点是代码被读的次数远多于被写的次数。这篇指南旨在提高代码的可读性,使浩瀚如烟的Python代码风格能保持一致。正如PEP 20那首《Zen of Python》的小诗里所说的:“可读性很重要(Readability counts)”。

这本风格指南是关于一致性的。同风格指南保持一致性是重要的,但是同项目保持一致性更加重要,同一个模块和一个函数保持一致性则最为重要。然而最最重要的是:要知道何时去违反一致性,因为有时风格指南并不适用。当存有疑虑时,请自行做出最佳判断。请参考别的例子去做出最好的决定。并且不要犹豫,尽管提问。

特别的:千万不要为了遵守这篇PEP而破坏向后兼容性!以下情况,则可以忽略这份风格指南:

  • 当采用风格指南时会让代码更难读,甚至对于习惯阅读遵循这篇PEP的代码的人来说也是如此。
  • 需要和周围的代码保持一致性,但这些代码违反了指南中的风格(可是时历史原因造成的)——尽管这可能也是一个收拾别人烂摊子的机会(进入真正的极限编程状态)。
  • 若是有问题的某段代码早于引入指南的时间,那么没有必要去修改这段代码。
  • 代码需要和更旧版本的Python保持兼容,而旧版本的Python不支持风格指南所推荐的特性。

代码布局

缩进

每个缩进级别采用4个空格。连续行所包装的元素应该要么采用Python隐式续行,即垂直对齐于()、[]或者{},要么采用悬挂缩进(hanging indent)。采用悬挂缩进时需考虑以下两点:第一行不应该包括参数,并且在续行中需要再缩进一级以便清楚表示。

正确写法:

#与左括号对齐(垂直对齐写法)
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)

错误写法:

#参数放在第一行而没有垂直对齐
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)

当 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',
    )

Tab还是空格?

推荐使用空格控制缩进。tab 只用于与之前的代码保持一致的情况。Python3 不允许混合使用 tab 和空格控制缩进。Python2 中混合使用 tab 和空格缩进的代码应改成只使用空格缩进。当使用 -t 选项调用 Python2 命令行解释器时,会给出混合使用 tab 和空格的警告。使用 -tt 选项时,这些警告会变为错误。我们推荐使用这些选项。

单行最大长度

将所有行都限制在79个字符长度以内。对于连续大段的文字(比如文档字符串(docstring)或注释),其结构上的限制更少,这些行应该被限制在72个字符长度内。限制编辑器的窗口宽度能让好几个文件同时打开在屏幕上显示,在使用代码评审(code review)工具时在两个相邻窗口显示两个版本的代码效果很好。

很多工具的默认自动换行会破坏代码的结构,使代码更难以理解。在窗口大小设置为80个字符的编辑器中,即使在换行时编辑器可能会在最后一列放置一个记号,为避免自动换行也需要限制每行字符长度。一些基于web的工具可能根本没有自动换行的功能。一些团队会强烈希望行长度比79个字符更长。当代码仅仅只由一个团队维护时,可以达成一致让行长度增加到80到100字符(实际上最大行长是99字符),注释和文档字符串仍然是以72字符换行。

Python标准库比较传统,将行长限制在79个字符以内(文档字符串/注释为72个字符)。一种推荐的换行方式是利用Python圆括号、方括号和花括号中的隐式续行。长行可以通过在括号内换行来分成多行。应该最好加上反斜杠来区别续行。有时续行只能使用反斜杠才。例如,较长的多个with语句不能采用隐式续行,只能接受反斜杠表示换行:

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

(参阅前面有关多行 if 语句的讨论,进一步考虑这里 with 语句的缩进。)

另一个这样的例子是 assert 语句。要确保续行的缩进适当。

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

长期以来,一直推荐的风格是在二元运算符之后换行,但是这会影响代码可读性,一是会使运算符分散在屏幕的不同列上,二是会使每个运算符留在前一行,并远离操作数分离。必须人为地判断应该加上或者减去哪些东西,这就增加了人眼的负担。

# 错误写法:运算符远离操作数
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

为了解决这一可读性问题,数学家和出版商遵循了相反的规约。Donald Knuth 在他的《Computers and Typesetting》丛书中解释了这一规约。“虽然段落中的公式总是在二元运算符后换行,但显示公式时总是在二元运算符之前换行。”

遵循数学上的传统可以写出可读性更好的代码。

# 正确写法:更容易匹配运算符与操作数
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

在 Python 代码中,只要前后保持一致,在二元操作符之前或之后换行都可以。对于新写的代码,建议使用 Knuth 推荐的风格。

空行

使用2个空行来分隔最外层的函数(function)和类(class)定义。

使用1个空行来分隔类中的方法(method)定义。

可以使用额外的空行(尽量少)来分隔一组相关的函数。在一系列相关的仅占一行的函数之间,空行也可以被省略(比如一组虚函数定义)。

在函数内使用空行(尽量少)使代码逻辑更清晰。

Python支持control-L(如:^L)换页符作为空格;许多工具将这些符号作为分页符,因此你可以使用这些符号来分页或者区分文件中的相关区域。注意,一些编辑器和基于web的代码预览器可能不会将control-L识别为分页符,而是显示成其他符号。

源文件编码

Python核心发行版中的代码应该一直使用UTF-8(Python 2中使用ASCII)。

使用ASCII(Python 2)或者UTF-8(Python 3)的文件不应该添加编码声明。

在标准库中,只有用作测试目的,或者注释或文档字符串需要提及作者名字而不得不使用非ASCII字符时,才能使用非默认的编码。否则,在字符串文字中包括非ASCII数据时,推荐使用\x, \u, \U或\N等转义符。

对于Python 3.0及其以后的版本中,标准库遵循以下原则(参见PEP 3131):Python标准库中的所有标识符都必须只采用ASCII编码的标识符,在可行的条件下也应当使用英文词(很多情况下,使用的缩写和技术术语词都不是英文)。此外,字符串文字和注释应该只包括ASCII编码。只有两种例外:

  • 测试情况下为了测试非ASCII编码的特性
  • 作者名字。作者名字不是由拉丁字母组成的也必须提供一个拉丁音译名。

鼓励具有全球受众的开放源码项目采用类似的原则。

模块引用(Imports)

导入语句应该分开写,而不是都放到一行,例如:

# 正确写法:
import os
import sys

# 错误写法:
import sys, os

# 这种写法也可以:
from subprocess import Popen, PIPE

导入语句一般放在文件开头,在模块注释和文档字符串之后,模块全局变量和常量之前。

注意分组导入,并遵循一下顺序:

  • 标准库导入
  • 相关的第三方包导入
  • 本地应用或库的特定导入

不同的组应使用空行分隔

推荐绝对路径导入方式,通常这样更有可读性,在系统配置错误时的情况下(比如包里的文件路径有以 sys.path 结尾的),也会表现得更好(至少会给出更好的错误提示信息):

import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

然而,清晰的相对路径导入方式也可以接受,特别是当包的结构比较复杂,使用绝对路径导入方式没有必要,反而会使代码显得冗杂。

from . import sibling
from .sibling import example

标准库的代码没有复杂的包结构,并且一直使用绝对路径的导入方式。不能使用隐式的相对路径导入,这在 Python3 中已经被移除。

当从包含类的模块中导入类时,可以使用以下写法:

import myclass
import foo.bar.yourclass

然后使用myclass.MyClass 或者foo.bar.yourclass.YourClass。

避免使用通配符的方式导入(from <module> import *),因为这会使当前命名空间中的名称含义不清晰,给读者和许多自动化工具造成困扰。有一种情况正当使用通配符导入的情形,就是将一个内部接口重新发布作为公共 API (比如,使用可选的加速模块中的定义覆盖纯 Python 实现的接口,预先无法知晓具体哪些定义将被覆盖)。

当使用这种方式重新发布名称时,指南后面关于公共和内部接口的部分仍然适用。

模块级双下划线名称

模块中的“双下滑线”(变量名以两个下划线开头,两个下划线结尾)变量,比如__all__,__author,__version__等,应该写在文档字符串(docstring)之后,除了form __future__引用(imports)的任何其它类型的引用语句之前。Python要求模块中__future__的导入必须出现在除文档字符串(docstring)之外的任何其他代码之前。

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

字符串引用(String Quotes)

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

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

表达式和语句中的空格

不能忍受的事情(Pet Peeves)

以下情形中要避免使用过多空格:

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

其他建议

避免任何行末空格,因为通常不可见,它们容易让人困惑。例如,\后跟一个空格和一个新行,\将不会被当作续行符。有些编辑器可以自动去除行末空格。对于这种情况,许多项目(如CPython本身)会用钩子做提交前检查。

这些二元运算符两边通常保留一个空格:赋值运算符(=),增强的复制运算符(+=,-=等)关系运算符(==,<,>,!=,<=,>=,in,not in,is,is not),布尔运算符(and,or,not)。

1、如果混用了不同优先级的运算符,可以考虑在低优先级运算符两边增加空格。但不要超过一个,并且要保持二元运算符两边的空格数量相同:

# 正确写法:
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)

2、当表示关键字参数或者默认参数时,等号两边不加空格:

# 正确写法:
def complex(real, imag=0.0):
  return magic(r=real, i=imag)

# 错误写法:
def complex(real, imag = 0.0):
  return magic(r = real, i = imag)

3、函数注释中的冒号遵循一般的加空格的规则,如果有箭头,要在其两边加空格。(更多信息请见下面的函数注释。)

# 正确写法:
def munge(input: AnyStr): ...
def munge() -> AnyStr: ...

# 错误写法:
def munge(input:AnyStr): ...
def munge()->PosInt: ...

4、当组合使用参数注释和参数默认值时,赋值号两边要加空格(但仅对既有空格又有默认值的参数成立)。

# 正确写法:
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): ...

5、通常不鼓励使用复合语句(即将多条语句写在一行)。

# 正确写法:
if foo == 'blah':
  do_blah_thing()
do_one()
do_two()
do_three()

# 最好不要这样写:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three() 

6、小型的if/for/whiel语句放在一行是可以的。但是有多条分句时不要这样做。也要避免无谓的换行!

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

何时在末尾加逗号

末尾的逗号通常是可选的。但是,在定义单元素元组时是必须的(而且在 Python2 中,逗号对 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个空格。

使用英文写作,参考Strunk和White的《The Elements of Style》

来自非英语国家的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.
"""

对于单行文档字符串,可以把末尾的”””放在同一行。

命名规约

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()函数返回一个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__。请依照文档描述来使用这些命名,千万不要自己发明。

规范:命名规约

需要避免的命名(Names To Avoid)

不要使用字符’l’(L的小写的字母),’O’(o大写的字母),或者’I’(i的大写的字母)来作为单个字符的变量名。

在一些字体中,这些字符和数字1和0无法区别开来。比如,当想使用’l’时,使用’L’代替。

ASCII兼容性(ASCII Compatibility)

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

包和模块命名(Package And Module Names)

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

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

类命名(Class Names)

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

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

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

类型变量命名(Type variable names)

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

from typing import TypeVar

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

异常命名(Exception Names)

由于异常实际上也是类,因此类命名约定也适用与异常。不同的是,如果异常实际上是抛出错误的话,异常名前应该加上”Error”的前缀。

全局变量命名(Global Variable Names)

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

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

函数命名(Function Names)

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

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

函数和方法参数(Function And Method Arguments)

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

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

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

方法命名和实例变量(Method Names And Instance Variables)

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

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

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

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

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

常量(Constants)

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

继承的设计(Designing For Inheritance)

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

  • 公开属性是那些你希望和你定义的类无关的客户来使用的,并且确保不会出现向后不兼容的问题。非公开属性是那些不希望被第三方使用的部分,你可以不用保证非公开属性不会变化或被移除。
  • 我们在这里没有使用“私有(private)”这个词,因为在Python里没有什么属性是真正私有的(这样设计省略了大量不必要的工作)。
  • 另一类属性属于子类API的一部分(在其他语言中经常被称为”protected”)。一些类是为继承设计的,要么扩展要么修改类的部分行为。当设计这样的类时,需要谨慎明确地决定哪些属性是公开的,哪些属于子类API,哪些真的只会被你的基类调用。
  • 请记住以上几点,下面是Python风格的指南:
  • 公开属性不应该有开头下划线。
  • 如果公开属性的名字和保留关键字有冲突,在你的属性名尾部加上一个下划线。这比采用缩写和简写更好。(然而,和这条规则冲突的是,‘cls’对任何变量和参数来说都是一个更好地拼写,因为大家都知道这表示class,特别是在类方法的第一个参数里。)
    • 注意 1:对于类方法,参考之前的参数命名建议。
  • 对于简单的公共数据属性,最后仅公开属性名字,不要公开复杂的调用或设值方法。请记住,如果你发现一个简单的数据属性需要增加功能行为时,Python为功能增强提供了一个简单的途径。这种情况下,使用Properties注解将功能实现隐藏在简单数据属性访问语法之后。
    • 注意 1:Properties注解仅仅对新风格类有用。
    • 注意 2:尽量保证功能行为没有副作用,尽管缓存这种副作用看上去并没有什么大问题。
    • 注意 3: 对计算量大的运算避免试用properties;属性的注解会让调用者相信访问的运算量是相对较小的。
  • 如果你的类将被子类继承的话,你有一些属性并不想让子类访问,考虑将他们命名为两个下划线开头并且结尾处没有下划线。这样会触发Python命名修饰算法,类名会被修饰添加到属性名中。这样可以避免属性命名冲突,以免子类会不经意间包含相同的命名。
    • 注意 1:注意命名修饰仅仅是简单地将类名加入到修饰名中,所以如果子类有相同的类名合属性名,你可能仍然会遇到命名冲突问题。
    • 注意 2:命名修饰可以有特定用途,比如在调试时,__getattr__()比较不方便。然而命名修饰算法的可以很好地记录,并且容意手动执行。
    • 注意 3:不是所有人都喜欢命名修饰。需要试着去平衡避免偶然命名冲突的需求和高级调用者使用的潜在可能性。

编程建议

1、代码应该以不影响其他Python实现(PyPy,Jython,IronPython,Cython,Psyco等)的方式编写。例如,不要依赖于 CPython 在字符串拼接时的优化实现,像这种语句形式a += b和a = a + b。即使是 CPython(仅对某些类型起作用) 这种优化也是脆弱的,不是在所有的实现中都不使用引用计数。在库中性能敏感的部分,用”.join形式来代替。这会确保在所有不同的实现中字符串拼接是线性时间的。

2、与单例作比较,像None应该用is或is not,从不使用==操作符。同样的,当心if x is not None这样的写法,你是不知真的要判断x不是None。例如,测试一个默认值为None的变量或参数是否设置成了其它值,其它值有可能是某种特殊类型(如容器),这种特殊类型在逻辑运算时其值会被当作Flase来看待。

3、用is not操作符而不是not … is。虽然这两个表达式是功能相同的,前一个是更可读的,是首选。

# 推荐的写法:
if foo is not None:

# 不推荐的写法:
if not foo is None:

4、用富比较实现排序操作的时候,最好实现所有六个比较操作符( __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()函数肯定会使用>操作符。当然,最好是六个操作符都实现,以便在其他情况下不会出现混淆。

5、始终使用def语句来代替直接绑定了一个lambda表达式的赋值语句。

# 推荐的写法:
def f(x): return 2*x

# 不推荐的写法:
f = lambda x: 2*x

第一个表单意味着生成的函数对象的名称是’f’而不是通用的’<lambda>‘。通常这对异常追踪和字符串表述是更有用的。使用赋值语句消除了使用lambda表达式可以提供,而一个显式的def语句不能提供的唯一好处,如,lambda能镶嵌在一个很长的表达式里。

6、异常类应派生自Exception而不是BaseException。直接继承BaseException是为Exception保留的,从BaseException继承并捕获异常这种做法几乎总是错的。设计异常的层次结构,应基于那些可能出现异常的代码,而不是引发异常的位置。编码的时候,以回答“出了什么问题?”为目标,而不是仅仅指出“这里出现了问题”(见PEP 3151一个内建异常结构层次的例子)。类的命名约定适用于异常,如果异常类是一个错误,你应该给异常类加一个后缀Error。用于非本地流程控制或者其他形式的信号的非错误异常不需要一个特殊的后缀。

7、适当的使用异常链。在 Python 3 里,应该使用raise X from Y来指示显式替换,而不会丢失原始的追溯。当有意替换一个内部的异常时(在 Python 2 用raise X,Python 3.3+ 用raise X from None),请确保将相关详细信息转移到新异常中(例如,将KeyError转换为AttributeError时保留属性名称,或将原始异常的文本嵌入到新的异常消息中)。

8、在 Python 2 里抛出异常时,用raise ValueError(‘message’)代替旧式的raise ValueError, ‘message’。在 Python 3 之后的语法里,旧式的异常抛出方式是非法的。使用括号形式的异常意味着,当你传给异常的参数过长或者包含字符串格式化时,你就不需要使用续行符了,这要感谢括号!

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

try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None

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

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

  • 如果异常处理程序会打印出或者记录回溯信息;至少用户意识到错误的存在。
  • 如果代码需要做一些清理工作,但后面用raise向上抛出异常。try .. finally是处理这种情况更好的方式。

10、绑定异常给一个名字时,最好使用 Python 2.6 里添加的明确的名字绑定语法:

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

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

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

12、对于所有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)

13、当某个资源仅被特定代码段使用,用with语句确保其在使用后被立即干净的清除了,try/finally也是也接受的。

14、当它们做一些除了获取和释放资源之外的事的时候,上下文管理器应该通过单独的函数或方法调用。例如:

# 推荐的写法:
with conn.begin_transaction():
do_stuff_in_transaction(conn)

# 不推荐的写法:
with conn:
    do_stuff_in_transaction(conn)

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

15、坚持使用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)

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

17、用”.startswith()和”.endswith()代替字符串切片来检查前缀和后缀。startswith()和endswith()是更简洁的,不容易出错的。例如:

#推荐的写法:
if foo.startswith('bar'):

#不推荐的写法:
if foo[:3] == 'bar':

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

#推荐的写法:
if isinstance(obj, int):

#不推荐的写法:
if type(obj) is type(1):

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

if isinstance(obj, basestring):

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

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

# 正确的写法:
if not seq:
if seq:

# 错误的写法:
if len(seq):
if not len(seq):

20、不要让字符串对尾随的空格有依赖。这样的尾随空格是视觉上无法区分的,一些编辑器(或者,reindent.py)会将其裁剪掉。

21、不要用==比较True和False。

#推荐的写法:
if greeting:

#不推荐的写法:
if greeting == True:

#更加不推荐的写法:
if greeting is True:

函数注解

  • 随着PEP 484被正式接受,函数注释的样式规则已经改变。
  • 为了向前兼容,Python 3代码中的函数注释最好使用PEP 484语法。(上一节中的有一些关于注解格式的建议。)
  • 建议不再使用在此文档早期版本中描述的试验性质的注解样式。
  • 然而,在标准库(stdlib)之外,现在鼓励在PEP 484的规则范围内的实验。例如,使用PEP 484样式类型的注解标记大型第三方库或应用程序,评估添加这些注解的方式是否简便,并观察其存在是否增加了代码可读性。
  • Python标准库在采用这些注解时应持谨慎态度,但是当编写新代码或进行大的重构时,允许使用。
  • 如果希望不按照函数注解的方式来使用函数,可以在文件头部添加以下注释:# type: ignore
  • 这会告诉类型检查器忽略所有注解。 (在PEP 484中可以找到更细致的方式来控制类型检查器的行为。)
  • 像代码扫描工具一样(linters),类型检查器是可选的,单独的工具。默认情况下,Python解释器不应该由于类型检查而发出任何消息,并且不应该根据注释来改变它们的行为。
  • 不想使用类型检查器的用户可以忽略它们。但是,预计第三方库软件包的用户可能希望在这些软件包上运行类型检查器。为此,PEP 484 建议使用存根文件:.pyi文件,类型检查器优先于相应的.py文件读取这个文件。存根文件可以与库一起分发,也可以单独地(在库作者许可的情况下)通过Typeshed repo7分发。
  • 对于需要向后兼容的代码,可以以注释的形式添加类型注解。参见PEP 484的相关章节。

变量注解

PEP 526 引入了变量注解。他们的风格建议与上面介绍的函数注解类似:

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

冒号前面不应该有空格。

如果赋值号有右值,那么等号两边都应该有一个空格。

# 推荐写法:
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

虽然 PEP 526 已被 Python3.6 所接受,但是对于所有 Python 版本中的存根文件,首选变量注解语法(详见 PEP 484)。

 

脚注:悬挂缩进是一种排版样式,除第一行外,其段落中的所有行都要缩进。在 Python 文本中,这一术语描述了这样一种样式:对于括号括起来的语句,该行的最后一个非空白字符是左括号,后续行都要缩进,直到右括号。

参考文档

版权

本文档没有版权限制,公众可随时参阅。

PEP 8的使用实践

下表概述了Python代码中的一些常见命名样式以及何时使用它们:

类型 命名公约 实例
function 使用小写单词。用下划线分隔单词以提高可读性。 function, my_function
variable 使用小写的单个字母、单词或单词。单独的单词与下划线,以提高可读性。 x, var, my_variable
class 每个单词以大写字母开头。不要用下划线分隔单词。这种式样叫做骆驼箱。 Model, MyClass
method 使用小写单词。单独的单词与下划线,以提高可读性。 class_method, method
constant 使用大写字母、单词或单词。单独的单词与下划线,以提高可读性。 CONSTANT, MY_CONSTANT, MY_LONG_CONSTANT
module 使用一个或多个小写单词。单独的单词与下划线,以提高可读性。 module.py, my_module.py
package 使用一个或多个小写单词。不要用下划线分隔单词。 package, mypackage

这些是一些常见的命名约定和如何使用它们的示例。但是,为了编写可读的代码,您仍然必须小心选择字母和单词。除了在代码中选择正确的命名样式之外,还必须仔细选择名称。下面是关于如何尽可能有效地做到这一点的几个提示。

用 Pycharm 编程,可以通过快捷键 Alt + Ctrl + L 将选中行(多行)自动 PEP 8 格式化,修改行内不符合PEP 8的地方,如果不选行直接按则修改整个文件,快速高效。

工具推荐:

  • autopep8:一个开源的命令行工具,它能够将Python代码自动格式化为PEP8风格。
  • Pylint:一个检查违反 PEP 8 规范和常见错误的库。它在一些流行的编辑器和 IDE 中都有集成,也可以单独从命令行运行。
  • flake8:将 PEP 8、Pyflakes(类似 Pylint)、McCabe(代码复杂性检查器)和第三方插件整合到一起,以检查 Python 代码风格和质量的一个 Python 工具
  • Yapf:另一种有自己的配置项列表的重新格式化代码的工具。它与 Autopep8 的不同之处在于它不仅会指出代码中违反 PEP 8 规范的地方,还会对没有违反 PEP 8 但代码风格不一致的地方重新格式化,旨在令代码的可读性更强。
  • Black:在代码检查工具当中算是比较新的一个。它与 Autopep8 和 Yapf 类似,但限制较多,没有太多的自定义选项。这样的好处是你不需要去决定使用怎么样的代码风格,让 Black 来给你做决定就好。

参考链接:

发表评论

邮箱地址不会被公开。 必填项已用*标注