(简体中文)PEP 8 -- Style Guide for Python Code

项目内容
版本8
标题Style Guide for Python Code
作者GuidovanRossum<guidoatpython.org>(下文简称 Guido)
BarryWarsaw<barryatpython.org>(下文简称 Barry)
NickCoghlan<ncoghlanatgmail.com>
创建时间2021年7月5日
更新记录2021年7月5日
2013年8月1日

若有 bug 或疑问,就在评论区告诉我吧 ฅ( ̳• ◡ • ̳)ฅ♪

因为原文的脚注比较复杂,CSDN尚不支持,所以我把脚注改用上标来表示。

概述

本文档是 Python 及其自带标准库中代码的编码约定。 关于 Python 的 C 语言实现的编码约定,请参阅《Style Guide for C Code》1

本文档和 PEP 257(文档字符串约定)是从 Guido 最初的Python样式指南文章中改编而来,并对Barry 的样式指南2进行了一些补充。

本文档会不断更新,因为编程语言本身的更改会使得原有的约定显得过时,所以本文档会不断变化以适应编程语言的改变。

许多软件项目都有自己的编码约定,而且与本文档不一致,此时,应优先使用那些项目自己的约定。

请根据实际情况选择编码约定

Guido 的主要见解之一是“代码被阅读的次数远多于其编写的次数”。因此,本文档旨在提高代码的可读性,并要求各个 Python 代码的软件项目保持一致。正如 PEP 20 中所说:“可读性至关重要”。

本文档目的是树立一致性的编码约定。一致性很重要,项目内的一致性更为重要,一个模块或功能内的一致性最为重要。

不过,也有本文档不适用的情况。 是否适用,这需要您根据实际情况自行判断,您无法判断时,可以查看模块或功能内的其他代码,实在不行,一定要向编写相关代码其他人不耻下问。

注意:不要为了遵守本文档而破坏了向后兼容性!

以下情况,本文档可能会不适用,请根据实际情况斟酌:

  • 应用本文档后,即使对于那些习惯于阅读遵循此文档的代码的人,也会使代码的可读性降低。
  • 为了与周围的代码一致。
  • 所讨论的代码是在本文档发布前所写,且代码无需改动。
  • 需要与旧代码兼容。

代码布局

缩进

使用4个空格来代表一个缩进级别。

续行应使用在括号、方括号、花括号内,如示例代码:

  • 垂直对齐括号中的元素
  • 使用悬挂缩进7。使用此方式时,应考虑以下几点: 第一行不写参数;增加一个缩进级别,将其清楚地区分为延续行。
# 正确的示例:

# 与左侧的括号对齐。
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)

对于续行,在对齐时,空格数量也可以不是4个。例如;

# 此处只用了两个空格来进行缩进,当然也可以用4个以上的空格来表示缩进。
foo = long_function_name(
  var_one, var_two,
  var_three, var_four)

如果 if 语句的条件部分太长,可以将其写成多行,有以下两种写法,请根据自身需要选用:

# 垂直对齐括号中的元素,但这种格式看起来会和 if 内部的语句混在一起。
if (this_is_one_thing and
    that_is_another_thing):
    do_something()

# 在此处添加此 if 的注释。
if (this_is_one_thing and
    that_is_another_thing):
    # 在此处添加条件为 True 时,要执行的代码的注释。
    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 3不允许混合使用制表符和空格进行缩进。

混合使用制表符和空格进行缩进的Python 2代码应改为仅使用空格。否则,
当使用 -t 选项调用Python 2命令行解释器时,它会发出有关非法混合使用制表符和空格的警告。 当使用 -tt 时,这些警告变为错误。 强烈建议您使用这两个选项!

限制每行的字符数量

一行不得超过79个字符。

对于文档字符串和注释,一行不得超过72个字符。

有些团队喜欢在一行中写较多的字符,但依旧不建议超过99个字符,而且注释和文档字符串仍不超过72个字符。

包装长行的首选方法是将表达式写在括号中,来将长行分成多行。应优先使用此方式,而不是使用反斜杠。

不过,有时还是要用到反斜杠。例如,长的多个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())

将长行分为多行后,需要增加缩进,来保证语法正确和可读性。(关于缩进,请查看前面“缩进”章节中的内容)

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

几十年来,推荐的写法是在二元运算符之后换行。 但这会以两种方式降低可读性:

  • 运算符会分散在屏幕的不同列上。
  • 每个操作数对应的运算符都在上一行。

在这中写法下,眼睛必须做额外的工作才能分辨出对应的操作数:

# 错误的示例:
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

为了解决此可读性问题,数学家们遵循相反的约定。 唐纳德·克努斯(Donald Knuth)在他的《Computers and Typesetting》3系列书籍中解释了传统规则:“尽管段落中的公式总是在二元运算符和关系之后中断,但显示的公式总是在二元运算符和关系之前中断”。

遵循此传统会使代码更具可读性:

# 正确的示例:
# 此写法,在阅读时更易于将运算符与操作数匹配。
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

对于旧代码,请遵循已有的写法,若旧代码中没有一致的写法,则建议使用唐纳德·克努斯的写法。对于新代码,也建议使用唐纳德·克努斯的写法。

空行

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

类内部的方法定义由单个空行包围。

空行还可以分隔相关功能的代码。有时,会遇到用虚线来分隔的代码,此时,不要用空行来包围虚线。

在函数中可使用空白行来对逻辑部分进行分隔。

Python接受control-L(即^ L)换页符作为空格。许多工具将这些字符视为页面分隔符,因此您可以使用它们来分隔文件相关部分的页面。请注意,某些编辑器和基于Web的代码查看器可能不会将control-L识别为换页,而是在其位置显示为一个标志符号。

源文件的编码

发布的 Python 核心代码将始终使用UTF-8(Python 2是ASCII)。

使用ASCII(在Python 2中)或UTF-8(在Python 3中)的文件不可使用编码声明。

在标准库中,仅一下情况才会使用非默认编码:

  • 用于测试目的。
  • 在 Python 2中,注释或文档字符串中作者姓名是非ASCII字符。否则,使用\ x、\ u、\ U或\ N转义是在字符串文字中包含非ASCII数据的首选方法。

对于Python 3.0及更高版本,标准库规定了以下策略(请参阅 PEP 3131):Python标准库中的所有标识符务必使用纯 ASCII 标识符,并且在可行的情况下应使用英文单词(在许多情况下,会使用非英语的缩写和技术术语)。此外,字符串文字和注释也必须使用ASCII。有以下两种例外情况:

  • 测试非ASCII功能的测试用例。
  • 作者的姓名。注:名称不基于拉丁字母(latin-1,ISO / IEC 8859-1字符集)的作者必须在此字符集中提供其姓名的音译。

建议具有全球受众的开源项目采用类似的政策。

Import功能

Import通常应放在单独的一行,每行只导入一个模块:

# 正确的示例:
import os
import sys
# 错误的示例:
import sys, os

同一个模块中的内容,写在一行中:

# 正确的示例:
from subprocess import Popen, PIPE

Import总是放在代码、模块全局变量和常量之前,并且紧随文件顶部的模块注释和文档字符串之后。

Import应按以下顺序分组,并在每组之间用空行隔开:

  • 标准库。
  • 相关第三方内容。
  • 本地应用程序或库。

推荐绝对导入。这样做会使代码更具可读性,并能提供更明确的错误消息:

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

但使用时,应这样写myclass.MyClassfoo.bar.yourclass.YourClass

应该避免使用通配符导入(例如from <module> import *),因为通配符使名称空间中不清楚存在哪些名称,这会混淆读取器和许多自动化工具。 不过,通配符导入也有一个合理的用例:将内部接口重新发布为公共API的一部分。

当以这种方式重新发布名称时,后面讲到的有关“公共接口”和“内部接口”的准则仍然适用。

模块级“Dunder”的名称

“Dunder”是指前后都带有两个下划线的变量或函数,诸如__all ____ author ____ version__,这些就是模块级“Dunder”,它们应放在模块文档字符串之后、除from __future__ imports之外的任何导入语句之前 (Python要求from __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对两种引号此没有任何偏向,选择一条规则并坚持下去即可。 但是,当字符串包含单引号或双引号字符时,请使用另一个以避免在字符串中使用反斜杠,以提高可读性。

对于三引号字符串,请始终使用三个双引号,而非三个单引号,这样可以与PEP 257中的文档字符串约定保持一致。

在表达式和语句中添加空格

避免使用多余的空格

紧靠在括号、方括号、大括号内时:

# 正确的示例:
spam(ham[1], {eggs: 2})
# 错误的示例:
spam( ham[ 1 ], { eggs: 2 } )

在尾随的逗号及后面的右括号之间:

# 正确的示例:
foo = (0,)
# 错误的示例:
bar = (0, )

在逗号、分号、冒号之前:

# 正确的示例:
if x == 4: print x, y; x, y = y, x
# 错误的示例:
if x == 4 : print x , y ; x , y = y , x

但是,在切片操作中,冒号的作用相当于二元运算符,并且在每一侧都应具有相等的数量(将其视为优先级最低的运算符)。 在扩展切片中,两个冒号必须应用相同的间距。 例外:省略slice参数时,将省略空格:

# 正确的示例:
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

其他建议

避免在任何行的结尾输入多余的空格。由于它通常是不可见的,因此可能会导致bug,却无法查出原因:反斜杠后换行是行继续标记,但若反斜杠后跟一个空格再换行符,则不算作行继续标记。 有些编辑器会自动删除结尾的空格,并且许多项目(例如CPython本身)都使用预提交钩子来防止出现结尾的空格。

在这些二进制运算符的两边要各输入一个空格:赋值(=),扩展赋值(+ =,-=等),比较(==,<,>,!=,<>,<=,> = ,in,not in,is,is not),布尔(and,or,not)。

如果使用优先级不同的运算符,请考虑在优先级最低的运算符周围添加空格。其余的空格可以根据情况自行增减, 但是,永远不要连续使用多个的空格,并且要保证在二进制运算符两边的空格数量相同:

# 正确的示例:
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 munge(input: AnyStr): ...
def munge() -> PosInt: ...
# 错误的示例:
def munge(input:AnyStr): ...
def munge()->PosInt: ...

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

# 正确的示例:
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): ...

通常,建议不使用复合语句(同一行上的多个语句):

# 正确的示例:
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()

何时使用尾随逗号

尾部的逗号通常是可选的,除了在组成一个元素的元组时它们是强制性的(并且在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,)

注释

错误的注释比没有注释更糟糕。 因此,当代码更改时,也要使注释保持最新状态!

注释应为完整的句子。 用英语写注释时,第一个单词应大写,除非它是一个以小写字母开头的标识符(请勿更改标识符的大小写!)。

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

在多句注释中,除了最后一句,您应该在每个句末的句号后使用两个空格。

确保您的注释清晰明了,并且对于其他使用该语言的使用者来说也很容易理解。

来自非英语国家的Python编码人员:请用英语写您的注释,除非您有120%的把握确保阅读该代码的人懂得你使用的语言。

块注释

块注释通常用于说明紧随其后的一些(或全部)代码,并且缩进到与该代码相同的级别。 块注释的每一行都以#和一个空格开头(除非注释中的文本是缩进的)。

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

内联注释

谨慎使用内联注释。

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

内联注释是不必要的,并且如果它们表明显而易见的内容,则会分散代码阅读者的注意力。 所以请不要这样做:

x = x + 1                 # x 加 1

但是有时候,为了说明一些简单的特殊作用,内联注释则很有用:

x = x + 1                 # 补偿边界

文档字符串

PEP 257中,对文档字符串的约定很完美。(文档字符串英文中有两种称呼,分别为 documentation strings 和 docstrings)

为所有公共模块、函数、类和方法编写文档字符串。 对于非公共方法,文档字符串不是必需的,但是您应该使用注释来描述该方法的作用。 该注释应出现在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-- 中文称之为驼峰式命名4,其由一个或多个单词组成,每个单词的首字母大写,其余字母小写,因其整体看起来凹凸不平而得名)。

    注意:在此命名样式中使用首字母缩略词时,将首字母缩略词的所有字母大写。 比如,HTTPServerError优于HttpServerError(HTTP 为 Hypertext Transfer Protocol 的首字母缩略词)。

  • 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关键字发生冲突,例如class_
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和零没有区别。 当尝试使用“ l”(小写字母,读作el)时,请改用“ L”。

ASCII 兼容性

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

包和模块的名称

模块应该有简短的全部为小写字母的名称。 仅在可以提高可读性的前提下,才能在模块名称中使用下划线。Python 包也应该有简短的全部为小写字母的名称,但不鼓励使用下划线。

当用 C 或 C++ 编写的扩展模块附带提供更高级别(例如,面向对象)接口的 Python 模块时,C/C++ 模块将具有前导下划线(例如_socket)。

类的名称

类名通常应使用CapWords约定。

当接口被文档化documented并主要用于公共调用时,此接口应使用函数的命名约定。

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

类型变量的名称

PEP 484 中引入的类型变量的名称通常应使用 CapWords 首选短名称:T、AnyStr、Num。 建议在用于声明协变量covariant或逆变量contravariant行为的变量中相应添加后缀_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__ 机制来防止导出全局变量,或者使用旧的约定,在此类全局变量前面加上下划线(这样做以表明这些全局变量是“模块非公开的”)。

函数和变量的名称

函数名应该是小写的,单词之间用下划线分隔以提高可读性。

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

混合大小写仅为了兼容已有现有代码时才能使用(例如 threading.py),以保持向后兼容性。

函数和方法的参数

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

始终使用名称cls作为类方法的第一个参数。

如果函数参数的名称与保留关键字发生冲突,通常最好附加一个尾随下划线,而不是使用缩写或拼写错误。 比如,class_clss好。 (如果允许,那么建议使用同义词来避免这种冲突。)

方法的名称和实例变量

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

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

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

Python 会将以下名称与类名混淆:如果类 Foo 具有名为 __a 的属性,则 Foo.__a 无法访问它。 (坚持使用的用户仍然可以通过调用 Foo._Foo__a 获得访问权限。)通常,双前导下划线应该仅用于避免与设计为子类化的类中的属性发生名称冲突。

注意:关于 __names 的使用存在一些争议(见下文)。

常数

常量通常在模块级别定义,并且所有字母都是大写、用下划线分隔单词。 示例: MAX_OVERFLOWTOTAL

关于继承的约定

总是事先决定好类的方法和实例变量(统称为“属性”)应该是公共的还是非公共non-public的。如还不能事先确定是否为公共的,则选择非公共;稍后将其改为公共,这将比公共属性改为非公共更容易。

公共属性是提供给类的不相关客户端使用的属性,并承诺更新会保持向后兼容。非公共属性是那些不打算被第三方使用的属性;您不必保证非公共属性不会更改甚至被删除。

我们在这里不使用术语“私有”,因为在 Python 中没有真正私有的属性(Python 中认为病没有必要声明是否私有,应当在使用时加以注意)。

另一类属性是“子类 API”(在其他语言中通常称为“受保护”)的一部分。某些类被设计为用于继承,以扩展或修改类行为的各个方面。在设计这样的类时,请注意明确决定哪些属性是公共的,哪些是子类 API 的一部分,哪些是真正仅供您的基类使用的。

考虑到这一点,以下是 PythonicPythonic指南:

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

  • 如果您的公共属性名称与保留关键字相冲突,请在您的属性名称后添加一个尾随下划线。 这比缩写或拼写错误更可取。 (然而,尽管有这个规则,对于已知是类的任何变量或参数,尤其是类方法的第一个参数,‘cls’ 是首选。)

注意 1:有关类方法,请参阅上文的参数名称建议。

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

注意 1:属性仅适用于新式类^New-style Classes^。

注意 2:尽量保持功能行为没有副作用,尽管缓存等副作用通常很好。

注意 3:避免将属性用于计算成本高的操作; 属性符号使调用者相信访问是(相对)便宜的。

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

注意 1:注意,在名称修饰^Name mangling^中,只使用简单的类名,因此如果子类选择相同的类名和属性名,仍然会出现名称冲突。

注意 2:名称修饰可以有某些用途,例如调试和 __getattr__()不太方便。 此时,名称名称修饰就显得有据可查且易于手动执行。

注意 3:并非每个人都喜欢名称修饰。 尝试平衡避免意外名称冲突的需要与潜在的高级调用者的使用。

公共和内部的接口

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

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

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

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

如果任何包含命名空间(包、模块或类)被视为内部的,则接口也被视为内部。

导入的名称应始终被视为实现细节。其他模块不得依赖于对此类导入名称的间接访问,除非它们是包含模块 API 的明确记录部分,例如 os.path 或从子模块公开功能的包的 __init__ 模块。

编程建议

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

    例如,不要依赖 CPython 对 a += ba = a + b形式的语句的就地字符串连接的高效实现。 这种优化即使在 CPython 中也是很容易被不小心破坏的(它仅适用于某些类型),并且在不使用引用计数的实现中根本不存在。 在库的性能敏感部分,应该使用''.join() 形式。 这将确保串联在各种实现中以线性时间发生。

  • 像 None 这样的单例,进行比较运算时,应该总是用 isis not 来完成,而不是使用相等运算符。

    另外,当你真正的意思是 if x is not None时,请注意写if x—— 例如,在测试默认为 None 的变量或参数是否设置为其他值时。 另一个值可能具有在布尔上下文中可能为 false 的类型(例如容器)!

  • 使用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 > xx < yy >= xx <= y 交换,并且可以交换x == yx != y 的操作数。 sort()min() 操作保证使用 < 操作符,而 max() 函数使用 >操作符。 但是,最好实现所有六个操作,以免在其他上下文中出现混淆。

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

# 正确的示例:
def f(x): return 2*x
# 错误的示例:
f = lambda x: 2*x

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

  • 从 Exception 而不是 BaseException 派生异常。 从 BaseException 直接继承是为异常保留的,在这些异常中捕获它们几乎总是错误的做法。

    根据捕获异常的代码可能需要的区别而不是引发异常的位置来设计异常层次结构。 旨在回答“出了什么问题?”这个问题。 以编程方式,而不是仅仅说明“发生了问题”(请参阅 PEP 3151 以获取有关为内置异常层次结构学习的课程示例)

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

  • 适当地使用异常链。 在 Python 3 中,“raise X from Y”应该用于指示显式替换而不丢失原始回溯。

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

  • 在 Python 2 中引发异常时,使用raise ValueError('message') 而不是旧形式 raise ValueError, 'message'

    后一种形式不是合法的 Python 3 语法。

    使用括号的形式还意味着当异常参数很长或包含字符串格式时,由于包含括号,您不需要使用行继续符。

  • 在捕获异常时,尽可能提及特定的异常,而不是使用一个空的except:子句:

try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None

一个空的 except:子句将捕获 SystemExit 和 KeyboardInterrupt 异常,使得用 Control-C 中断程序变得更加困难,并且可以掩盖其他问题。 如果要捕获所有表示程序错误的异常,请使用except Exception:(空的“except”等价于except BaseException:)。

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

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

  2. 如果代码需要做一些清理工作,然后让异常随着 raise 向上传播。 try…finally 可以成为处理这种情况的更好方法。

  • 当绑定捕获到相应名称的异常时,首选python2.6中添加的显式名称绑定语法:
try:
    process_data()
except Exception as exc:
    raise DataProcessingFailedError(str(exc))

这是 Python 3 中唯一支持的语法,并避免了与旧的基于逗号的语法相关的歧义问题。

  • 在捕获操作系统错误时,与 errno 值的内省introspection相比,更喜欢 Python 3.3 中引入的显式异常层次结构。

  • 此外,对于所有 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 语句也是有这种效果的。

  • 上下文管理器^Context managers^在执行除获取和释放资源之外的其他操作时,应通过单独的函数或方法调用:

# 正确的示例:
with conn.begin_transaction():
    do_stuff_in_transaction(conn)
# 错误的示例:
with conn:
    do_stuff_in_transaction(conn)

后一个示例没有提供任何信息来表明,除了在事务之后关闭连接, __enter____exit__ 方法还正在执行其他操作。 在这种情况下,明确表明是很重要的。

  • 在 return 语句中保持一致。 函数中的所有 return 语句都应该返回一个表达式,或者它们都不应该返回。 如果任何 return 语句返回一个表达式,则任何没有返回值的 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 中,str 和 unicode 有一个共同的基类 basestring,因此您可以执行以下操作:

if isinstance(obj, basestring):

请注意,在 Python 3 中,unicode 和 basestring 不再存在(只有 str)并且 bytes 对象不再是一种字符串(而是一个整数序列)。

  • 对于序列(字符串、列表、元组),使用空序列为假的事实:
# 正确的示例:
if not seq:
if seq:
# 错误的示例:
if len(seq):
if not len(seq):
  • 不要编写依赖重要尾随空格的字符串文字。 这种尾随空白在视觉上无法区分,有些编辑器(reindent.py)会修剪它们。

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

# 正确的示例:
if greeting:
# 错误的示例:
if greeting == True:

千万不要这样写:

# 错误的示例:
if greeting is True:
  • 不鼓励在 try...finally 的 finally 套件中使用流控制语句return/break/continue ,其中流控制语句会跳出 finally 套件。 这是因为此类语句将隐式取消通过 finally 套件传播的任何活动异常:
# 错误的示例:
def foo():
    try:
        1 / 0
    finally:
        return 42

函数的注释

随着 PEP 484 的接受,函数注释的样式规则正在发生变化。

  • 为了向前兼容,Python 3 代码中的函数注释最好使用 PEP 484 语法。 (上一节中有一些注释的格式建议。)

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

  • 但是,在标准库之外,现在鼓励在 PEP 484 规则内进行实验。 例如,使用 PEP 484 样式类型注释标记大型第三方库或应用程序,查看添加这些注释的难易程度,并观察它们的存在是否提高了代码的可理解性。

  • Python 标准库在采用此类注释时应该是保守的,但允许在新代码和大型重构中使用它们。

  • 对于想要对函数注释进行不同用途的代码,建议添加以下形式的注释:

# type: ignore

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

  • 与 linterlinter 一样,类型检查器是可选的、独立的工具。 默认情况下,Python 解释器不应由于类型检查而发出任何消息,也不应根据注释更改其行为。

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

  • 对于需要向后兼容的代码,可以用注释的形式添加类型注解。 请参阅 PEP 484 的相关部分6

变量的注释

PEP 526 引入了变量注释。 对它们的风格建议类似于上文函数注释:

  • 模块级变量、类、实例变量、局部变量的注释应该在冒号后有一个空格。
  • 冒号前不应该有空格。
  • 如果要将等于号右边的内容赋值,那么等号两边应该各有一个空格:
# 正确的示例:

code: int

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

code:int  # 在冒号后面没有空格
code : int  # 在冒号前有空格

class Test:
    result: int=0  # 在等于号前后没有空格
  • 尽管 Python 3.6 支持 PEP 526,但变量注释语法是所有 Python 版本上存根文件的首选语法(有关详细信息,请参阅 PEP 484)。

参考

{1}:PEP 7, 《Style Guide for C Code》, 作者 Guido van Rossum

{2}:Barry 的 《GNU Mailman style guide》http://barry.warsaw.us/software/STYLEGUIDE.txt

{3}:来自 Donald Knuth 的 《The TeXBook》, 第 195 和 196 页.

{4}:维基百科http://www.wikipedia.com/wiki/CamelCase

{5}:类型库https://github.com/python/typeshed

{6}:跨界代码和 Python 2.7 的建议语法https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code

{7}:悬挂缩进是一种排版样式,其中除第一行外,段落中的所有行都缩进。 在 Python 的上下文中,该术语用于描述一种样式,其中带括号的语句的左括号是该行的最后一个非空白字符,后续行缩进直到右括号。

版权

本文档已被置于公共领域。

来源:https://github.com/python/peps/blob/master/pep-0008.txt

术语表

{covariant}: 协变量。
class typing.TypeVar中,通过covariant=Truecontravariant=True可以把类型变量标记为协变量或逆变量。

{contravariant}: 逆变量。
class typing.TypeVar中,通过covariant=Truecontravariant=True可以把类型变量标记为协变量或逆变量。

{non-public}: 非公共。
在Python中,不使用术语“私有”,而是使用公共和非公共,并且在 Python 中不存在只能从对象内部访问的“私有”实例变量。

{New-style Classes}: 新式类。
新式类已集成到 Python 2.7 中,旧式类已在 Python 3 中删除。

{linter}:检查代码风格和错误的一个小工具。

{Pythonic}: 这是个Python自创的单词。
意思是按照Python的编码风格约定来写出极具Python特色的代码,别人仅需通过编码风格就能确定这是Python代码。

{lambda expression}: Lambda 表达式。
指匿名函数,即没有函数名的函数。

{documented}: 文档化。
即,被写入了使用文档。

{introspection}: 内省,或类型内省。
是在运行时进行的一种对象检测机制,其可以获取一个对象的所有信息,比如类型、属性等等。

{Context managers}: 上下文管理器。
用于精确地分配和释放资源。

{Name mangling}: 名称修饰,或名称改写。
在类中,__attributes 形式的任何标识符(前面至少两个下划线,后面至多一个下划线)将被替换为_class__attributes,其中class是当前类的名字。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值