Python PEP8 编码规范

PEP: 8
Title: Style Guide for Python Code
Version: c451868df657
Last-Modified: 2016-06-08 10:43:53 -0400 (Wed, 08 Jun 2016)
Author: Guido van Rossum <guido at python.org>, Barry Warsaw <barry at python.org>, Nick Coghlan <ncoghlan at gmail.com>
Status: Active
Type: Process
Content-Type: text/x-rst
Created: 05-Jul-2001
Post-History: 05-Jul-2001, 01-Aug-2013

  • 介绍
  • 愚蠢的使用一致性是无知的怪物(A Foolish Consistency is the Hobgoblin of Little Minds)
  • 代码布局
    • 缩进
    • 制表符还是空格
    • 每行最大字符数
    • 在二元运算符之前是否换行
    • 空行
    • 源文件编码
    • 导入
    • 模块级的双下划线(dunder)名称
    • 字符串引号
  • 表达式和语句中的空格
    • 不可容忍
    • 其他建议
  • 注释
    • 块注释
    • 行内注释
    • 文档字符串
  • 命名规范
    • 至高准则
    • 描述:命名风格
    • 约定俗成命名规定
      • 应避免的名称
      • 包名和模块名
      • 类名
      • 异常名
      • 全局变量名
      • 函数名
      • 函数和方法参数
      • 方法名和实例变量
      • 常量
      • 继承的设计
    • 公共和内部的接口
  • 编程建议
    • 功能注释
  • 参考

Introduction 介绍

本文档提供了Python代码的编码约定,包括主Python发行版中的标准库。请参阅Python的C实现中描述C代码样式指南的配套信息PEP [1]

本文和PEP 257(Docstring约定)改编自Guido的原始Python风格指南文章,并附有Barry的风格指南[2]

随着时间的推移,随着语言本身的变化,过去的约定被淘汰,这种风格指南随着时间的推移而不断发展。

许多项目都有自己的编码风格指南。如果发生任何冲突,此类项目特定指南优先于该项目。

愚蠢的使用一致性是无知的怪物(A Foolish Consistency is the Hobgoblin of Little Minds)

Guido的一个重要观点,代码的读取频率远高于编写代码。此处提供的准则旨在提高代码的可读性,并使其在各种Python代码中保持一致。正如PEP 20所说,“可读性很重要”。

风格指南是关于一致性的。与此风格指南的一致性非常重要。项目内的一致性更为重要。一个模块或功能内的一致性是最重要的。

但是,知道何时不一致。有时风格指南建议不适用。如有疑问,请使用您的最佳判断。查看其他示例并确定最佳效果。并且不要犹豫,不要问!

特别是:不要为了遵守这个PEP而破坏向后兼容性!

忽略特定指南的其他一些好理由:

  1. 在应用指南时,即使是习惯于阅读此PEP之后的代码的人,也会使代码的可读性降低。
  2. 为了与周围的代码保持一致(也许是出于历史原因) - 虽然这也是一个清理别人的混乱的机会(真正的XP风格)。
  3. 因为有问题的代码早于指南的引入,所以没有其他理由可以修改该代码。
  4. 当代码需要与不支持样式指南推荐的功能的旧版Python兼容时。

代码布局

缩进

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

Python可以使用包含在小括号,中括号和大括号内的方式使每行内容连续,或使用悬挂缩进,垂直对齐包装元素。使用悬挂缩进时,应考虑以下因素; 第一行应该没有参数,应该使用进一步的缩进来明确区分自己作为延续线。

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

Yes:

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

No:

# 有变量在第一行时禁止使用悬挂缩进
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)

四空格的规则对于续行是可选的。

Option:

# 悬挂缩进*可以*缩进量可以不是4个空格
foo = long_function_name(
  var_one, var_two,
  var_three, var_four)

当if语句的条件部分足够长以要求它跨多行写入时,值得注意的是两个字符关键字(即if)加上一个空格一个左括号的组合,创建了一个自然的多行条件的后续行的4空格缩进。这可能与嵌套在if语句中的缩进代码集产生视觉冲突,该代码集自然也会缩进到4个空格。该PEP没有明确地说明如何(或是否)进一步在视觉上将这些条件线与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 3不允许混合使用制表符和空格来缩进。
使用制表符和空格的混合缩进的Python 2代码应该转换为仅使用空格。
当使用-t选项调用Python 2命令行解释器时,它会发出有关非法混合制表符和空格的代码的警告。使用-tt时,这些警告会出错。强烈推荐这些选项!

每行最大字符数

将所有行限制为最多79个字符。

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

限制所需的编辑器窗口宽度使得可以并排打开多个文件,并且在使用在相邻列中显示两个版本的代码审查工具时可以正常工作。

大多数工具中的默认包装会破坏代码的可视化结构,使其更难理解。选择限制是为了避免在窗口宽度设置为80的情况下包装在编辑器中,即使工具在包装线条时在最终列中放置标记字形。某些基于Web的工具可能根本不提供动态换行。

有些团队强烈倾向于更长的线路长度。对于专门或主要由可以就此问题达成一致的团队维护的代码,可以将标称行长度从80个字符增加到100个字符(有效地将最大长度增加到99个字符),前提是评论和文档字符串仍然包装72个字符。

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

包装长行的首选方法是在小括号,中括号和大括号内使用Python隐含的行继续。通过在括号中包装表达式,可以在多行上分割长行。这些应该优先于反斜杠的行继续。

反斜杠的行继续在以下情形适用。例如,long,多重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语句。
确保在续行进行适当的缩进。

在二元运算符之前是否换行

几十年来,推荐的风格是在二元运算符之后打破。但这会以两种方式损害可读性:操作员倾向于分散在屏幕上的不同列上,并且每个操作符都会从其操作数移到前一行。在这里,眼睛必须做额外的工作来分辨哪些对象使用加法以及哪些对象使用减法:

# No: 对象远离操作符
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

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

遵循数学传统通常会产生更易读的代码:

# Yes:运算符和操作数很容易进行匹配
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

在Python代码中,只要约定在本地一致,就允许在二元运算符之前或之后中断。对于新代码,建议使用Knuth的样式。

空行

使用两个空行环绕顶级函数和类定义。

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

可以使用额外的空白行(谨慎地)来分离相关功能组。在一堆相关的单行(例如,一组虚拟实现)之间可以省略空行。

在函数中使用空行,谨慎地指示逻辑部分。

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

源文件编码

核心Python发行版中的代码应始终使用UTF-8(或Python 2中的ASCII)。

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

在标准库中,非默认编码应仅用于测试目的,或者当注释或文档字符串需要提及包含非ASCII字符的作者名称时; 否则,使用\x,\u,\U或\N转义是在字符串文字中包含非ASCII数据的首选方法。

对于Python 3.0及更高版本,标准库规定了以下策略(参见PEP 3131):Python标准库中的所有标识符必须使用仅ASCII标识符,并且应尽可能使用英语单词(在许多情况下,缩写和技术)使用的术语不是英语)。此外,字符串文字和注释也必须是ASCII格式。唯一的例外是

  1. 测试非ASCII功能的测试用例;
  2. 作者姓名。名字不是基于拉丁字母的作者必须提供他们名字的拉丁音译。

鼓励全球受众的开源项目采用类似的政策。

导入

  • 导入通常在分开的行,例如:
# Yes: 分开的行
import os
import sys

# No: 在一行
import sys, os

但是可以这样:

from subprocess import Popen, PIPE
  • 导入总是位于文件的顶部,在模块注释和文档字符串之后,在模块的全局变量与常量之前。
    导入应该按照以下顺序分组:

    1. 标准库导入
    2. 相关第三方库导入
    3. 本地应用/库特定导入
      你应该在每一组导入之间加入空行。
  • 建议使用绝对导入,因为如果导入系统配置不正确(例如,当包中的目录最终出现在sys.path上时),它们通常更具可读性并且往往表现得更好(或至少提供更好的错误消息):

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

然而,显示的指定相对导入路径是使用绝对路径的一个可接受的替代方案,特别是在处理使用绝对路径导入不必要冗长的复杂包布局时:

from . import sibling
from .sibling import example

标准库代码应避免复杂的包布局,并始终使用绝对导入。
永远不应该使用隐式相对导入,并且已经在Python 3中删除了。

  • 从包含类的模块导入类时,通常可以拼写:
from myclass import MyClass
from foo.bar.yourclass import YourClass

如果此拼写导致本地名称冲突,则拼写它们

import myclass
import foo.bar.yourclass

并使用“myclass.MyClass”和“foo.bar.yourclass.YourClass”。

  • 应该避免使用通配符导入(来自<module> import *),因为它们不清楚命名空间中存在哪些名称,使读者和许多自动化工具混淆。通配符导入有一个可防御的用例,即将内部接口重新发布为公共API的一部分(例如,使用可选加速器模块中的定义覆盖接口的纯Python实现,以及确切的定义将是被覆盖的事先不知道)。

以这种方式重新发布名称时,以下有关公共和内部接口的指南仍然适用。

模块级的双下划线(dunder)名称

模块级“dunder名“(以双下划线作为开头和结尾的名称),如allauthorversion等,除了future以为,应被放置在模块文档字符串之后,以及除from future 之外的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

字符串引号

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

对于三引号字符串,始终使用双引号字符与PEP 257中的docstring约定一致。

表达式和语句中的空格

不可容忍

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

紧靠括号,括号或括号内。

Yes: spam(ham[1], {eggs: 2})
No:  spam( ham[ 1 ], { eggs: 2 } )
  • 在逗号,分号或冒号之前:
Yes: if x == 4: print x, y; x, y = y, x
No:  if x == 4 : print x , y ; x , y = y , x
  • 但是,在切片中,冒号的行为类似于二元运算符,并且两侧的数量应该相等(将其视为具有最低优先级的运算符)。在扩展切片中,两个冒号必须具有相同的间距。例外:省略slice参数时,省略空格。

Yes:

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]

No:

ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
  • 紧接在启动函数调用的参数列表的左括号之前:
Yes: spam(1)
No:  spam (1)
  • 紧接在开始索引或切片的左括号之前:
Yes: dct['key'] = lst[index]
No:  dct ['key'] = lst [index]
  • 赋值(或其他)运算符周围有多个空格,以使其与另一个运算符对齐。

Yes:

x = 1
y = 2
long_variable = 3

No:

x             = 1
y             = 2
long_variable = 3

其他建议

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

  • 始终围绕这些二元运算符,两边都有一个空格:赋值(=),扩充赋值(+=, -= 等),比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not),布尔(and, or, not)。

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

Yes:

i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)

No:

i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
  • 当用于指示关键字参数或默认参数值时,请勿在=符号周围使用空格。

Yes:

def complex(real, imag=0.0):
    return magic(r=real, i=imag)

No:

def complex(real, imag = 0.0):
    return magic(r = real, i = imag)
  • 函数注释应该使用冒号的常规规则,并且如果存在,则始终在->箭头周围留出空格。(有关函数注释的更多信息,请参阅 下面的函数注释。)

Yes:

def munge(input: AnyStr): ...
def munge() -> AnyStr: ...

No:

def munge(input:AnyStr): ...
def munge()->PosInt: ...
  • 当给有类型备注的参数赋值的时候,在=两边添加空格(仅针对那种有类型备注和默认值的参数)。

Yes:

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

No:

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

Yes:

if foo == 'blah':
    do_blah_thing()
do_one()
do_two()
do_three()

Rather not:

if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
  • 虽然有时候将小的代码块和 if/for/while 放在同一行没什么问题,多行语句块的情况不要这样用,同样也要避免代码行太长!

Rather not:

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

Definitely not:

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

注释

与代码相矛盾的注释比没有注释更糟糕。始终优先考虑在代码更改时保持评论的最新状态!
注释应该是完整的句子。如果注释是短语或句子,则其第一个单词应该大写,除非它是以小写字母开头的标识符(永远不会改变标识符的情况!)。

如果注释很短,则可以省略最后的句点。块注释通常由完整句子构成的一个或多个段落组成,每个句子应以句点结束。

在句子结束期后你应该使用两个空格。

当用英文书写时,遵循Strunk and White 的书写风格。
来自非英语国家的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 -- 因其字母凹凸不平而得名[4])。这有时也被称为StudlyCaps。
    注意:当在首字母大写的风格中用到缩写时,所有缩写的字母用大写,因此,HTTPServerError 比 HttpServerError 好。
  • mixedCase (不同于首字母大写,第一个单词的首字母小写)
  • Capitalized_Words_With_Underscores (ugly! )

还有使用简短唯一前缀将相关名称组合在一起的风格。这在Python中并不常用,但为了完整性而提到它。例如,os.stat()函数返回一个元组,其元素传统上具有st_mode, st_size,st_mtime等名称。(这样做是为了强调与POSIX系统调用struct的字段的对应关系,这有助于程序员熟悉它。)
X11库的所有公共函数都加了前缀X。在Python里面没必要这么做,因为属性和方法在调用的时候都会用类名做前缀,函数名用模块名做前缀。
另外,下面这种用前缀或结尾下划线的特殊格式是被认可的(通常和一些约定相结合):

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

  • single_trailing_underscore_:(单下划线结尾)这是避免和Python内部关键词冲突的一种约定,比如:

    Tkinter.Toplevel(master, class_=’ClassName’)
    
  • __double_leading_underscore:在命名一个class属性时,调用name mangling(在类FooBar中,__ boo变成 _FooBar__boo ;见下文)。

  • __double_leading_and_trailing_underscore__:生成在用户控制的命名空间中的“魔术”对象或属性。例如__init ____import____file__。不要发明这样的名字; 仅按记录使用它们。

约定

应避免的名字

永远不要使用字母‘l’(小写的L),‘O’(大写的O),或者‘I’(大写的I)作为单字符变量名。
在有些字体里,这些字符无法和数字0和1区分,如果想用‘l’,用‘L’代替。

包名和模块名

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

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

类名

类名通常应使用CapWords约定。

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

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

异常名

因为异常应该是类,所以类命名约定适用于此处。但是,您应该在异常名称上使用后缀"Error"(如果异常实际上是错误)。

全局变量名

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

设计为通过M import *使用的模块应该使用__all__机制来防止输出全局变量,或者使用旧的约定为这些全局变量添加下划线(您可能希望这样做以表明这些全局变量是“模块非公开的” )。

函数名

函数名应该小写,如果想提高可读性可以用下划线分隔。
大小写混合仅在为了兼容原来主要以大小写混合风格的情况下使用(比如 threading.py),保持向后兼容性。

函数和方法参数

始终要将 self 作为实例方法的的第一个参数。
始终要将 cls 作为类静态方法的第一个参数。
如果函数的参数名和已有的关键词冲突,在最后加单一下划线比缩写或随意拼写更好。因此 class_ 比 clss 更好。(也许最好用同义词来避免这种冲突)

方法名和实例变量

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

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

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

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

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

常量

常量通常在模块级别定义,并以全部大写字母书写,下划线分隔单词。示例包括 MAX_OVERFLOWTOTAL

继承的设计

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

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

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

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

考虑到这一点,这是Pythonic指南:

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

如果公共属性名称与保留关键字冲突,请在属性名称后附加单个尾随下划线。这比缩写或损坏的拼写更可取。(但是,尽管有这个规则,'cls'是任何已知为类的变量或参数的首选拼写,尤其是类方法的第一个参数。)

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

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

注1:属性仅适用于新式类。

注2:尝试保持功能行为副作用免费,尽管缓存等副作用通常很好。

注3:避免使用属性进行计算成本高昂的操作; 属性表示法使调用者相信访问(相对)便宜。

如果您的类要进行子类化,并且您具有不希望使用子类的属性,请考虑使用双前导下划线和没有尾随下划线来命名它们。这将调用Python的名称修改方法,其中类的名称被修改为属性名称。如果子类无意中包含具有相同名称的属性,这有助于避免属性名称冲突。

注1:请注意,在修改的名称中只使用简单的类名,因此如果子类选择相同的类名和属性名,则仍然可以获得名称冲突。

注2:名称修改可以使某些用途,例如调试和 __getattr__(),不太方便。但是,名称修改算法已有详细记录,并且易于手动执行。

注3:不是每个人都喜欢误拼。尽量在避免意外姓名冲突与潜在的高级调用间寻求平衡。

公共和内部的接口

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

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

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

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

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

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

编程建议

  • 代码应该用不损害其他Python实现的方式去编写(PyPy,Jython,IronPython,Cython,Psyco 等)。
    比如,不要依赖于在CPython中高效的内置字符连接语句 a += b 或者 a = a + b。这种优化甚至在CPython中都是脆弱的(它只适用于某些类型)并且没有出现在不使用引用计数的实现中。在性能要求比较高的库中,可以种''.join() 代替。这可以确保字符关联在不同的实现中都可以以线性时间发生。
  • 和像None这样的单例对象进行比较的时候应该始终用 is 或者 is not,永远不要用等号运算符。
    另外,如果你在写 if x 的时候,请注意你是否表达的意思是 if x is not None。举个例子,当测试一个默认值为None的变量或者参数是否被设置为其他值的时候。这个其他值应该是在上下文中能成为bool类型false的值。
  • 使用 is not 运算符,而不是 not … is 。虽然这两种表达式在功能上完全相同,但前者更易于阅读,所以优先考虑。

Yes:

if foo is not None:

No:

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表达式绑定到一个变量上。

Yes:

def f(x): return 2*x

No:

f = lambda x: 2*x

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

  • Exception而不是BaseException派生异常。BaseException的直接继承保留用于捕获它们的异常几乎总是错误的事情。

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

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

类的命名规范适用于这里,但是你需要添加一个“Error”的后缀到你的异常类,如果异常是一个Error的话。非本地流控制或者其他形式的信号的非错误异常不需要特殊的后缀。
  • 适当地使用异常链接。在Python 3里,为了不丢失原始的根源,可以显式指定raise X from Y作为替代。
    当故意替换一个内部异常时(Python 2 使用“raise X”, Python 3.3 之后 使用 raise X from None),确保相关的细节转移到新的异常中(比如把AttributeError转为KeyError的时候保留属性名,或者将原始异常信息的文本内容内嵌到新的异常中)。

  • 在Python 2中抛出异常时,使用 rasie ValueError('message') 而不是用老的形式 raise ValueError, 'message'
    第二种形式在Python3 的语法中不合法
    使用小括号,意味着当异常里的参数非常长,或者包含字符串格式化的时候,不需要使用换行符。

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

try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None

如果只有一个except 块将会捕获到SystemExitKeyboardInterrupt异常,这样会很难通过Control-C中断程序,而且会掩盖掉其他问题。如果你想捕获所有指示程序出错的异常,使用 except Exception:(只有except等价于 except BaseException:)。
两种情况不应该只使用excpet块:

  1. 如果异常处理的代码会打印或者记录log;至少让用户知道发生了一个错误。

  2. 如果代码需要做清理工作,使用 raise..try…finally 能很好处理这种情况并且能让异常继续上浮。

    • 当给捕捉的异常绑定一个名字时,推荐使用在Python 2.6中加入的显式命名绑定语法:
try:
    process_data()
except Exception as exc:
    raise DataProcessingFailedError(str(exc))

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

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

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

Yes:

try:
    value = collection[key]
except KeyError:
    return key_not_found(key)
else:
    return handle_value(value)

No:

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也可以。
  • 无论何时获取和释放资源,都应该通过单独的函数或方法调用上下文管理器。举个例子:

Yes:

with conn.begin_transaction():
    do_stuff_in_transaction(conn)

No:

with conn:
    do_stuff_in_transaction(conn)

第二个例子没有提供任何信息去指明__enter____exit__方法在事务之后做出了关闭连接之外的其他事情。这种情况下,明确指明非常重要。

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

Yes:

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)

No:

def foo(x):
    if x >= 0:
        return math.sqrt(x)

def bar(x):
    if x < 0:
        return
    return math.sqrt(x)
  • 使用字符串方法代替字符串模块。
    字符串方法总是更快,并且和unicode字符串分享相同的API。如果需要兼容Python2.0之前的版本可以不用考虑这个规则。
  • 使用 .startswith().endswith() 代替通过字符串切割的方法去检查前缀和后缀。
    startswith()endswith()更干净,出错几率更小。比如:
Yes: if foo.startswith('bar'):
No: if foo[:3] == 'bar':
  • 对象类型的比较应该用isinstance()而不是直接比较type。
Yes: if isinstance(obj, int):

No: if type(obj) is type(1):

当检查一个对象是否为string类型时,记住,它也有可能是unicode string!在Python2中,strunicode都有相同的基类:basestring,所以你可以这样:

if isinstance(obj, basestring):

注意,在Python3中,unicodebasestring都不存在了(只有str)并且bytes类型的对象不再是string类型的一种(它是整数序列)

  • 对于序列来说(stringsliststuples),可以使用空序列为false的情况。
Yes: if not seq:
      if seq:

No: if len(seq):
      if not len(seq):
  • 书写字符串时不要依赖单词结尾的空格,这样的空格在视觉上难以区分,有些编辑器会自动去掉他们(比如 reindent.py)。
  • 不要用 == 去和True或者False比较:
Yes: if greeting:
No: if greeting == True:
Worse: if greeting is True:

功能注释

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

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

  • 不再鼓励先前在本PEP中推荐的注释样式的实验。

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

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

  • 对于想要对函数注释进行不同使用的代码,建议对表单进行注释:

# type: ignore

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

  • 像linters一样,类型检查器是可选的,单独的工具。默认情况下,Python解释器不应由于类型检查而发出任何消息,并且不应基于注释更改其行为。

  • 不想使用类型检查器的用户可以自由地忽略它们。但是,预计第三方库包的用户可能希望在这些包上运行类型检查器。为此, PEP 484建议使用存根文件:.pyi文件由类型检查程序读取,而不是相应的.py文件。存根文件可以与库一起分发,也可以通过类型化仓库[5]单独(与库作者的许可)一起分发。

  • 对于需要向后兼容的代码,可以以注释的形式添加功能注释。参见PEP 484 [6]的相关章节 。

参考

[1] PEP 7, Style Guide for C Code, van Rossum

[2] Barry's GNU Mailman style guide http://barry.warsaw.us/software/STYLEGUIDE.txt

[3] Donald Knuth's The TeXBook, pages 195 and 196.

[4] http://www.wikipedia.com/wiki/CamelCase

[5] Typeshed repo https://github.com/python/typeshed

[6] | Suggested syntax for Python 2.7 and straddling code https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 一 代码编排 缩进 4个空格的缩进(编辑器都可以完成此功能),不使用Tap,更不能混合使用Tap和空格。 每行最大...
    随风化作雨阅读 343评论 0 1
  • 更新时间:2016/5/13 介绍 本文档所提供的编码规范,适用于主要的Python发行版中组成标准库的Pytho...
    超net阅读 5,805评论 0 15
  • 二零一一年一月十四日 天气 Wind 最近几天每天晚上依旧的汗如雨下,报纸是一张一张被滴透,报架上的报纸也慢慢薄起...
    啊Ben阅读 161评论 0 5
  • 这周,我们读完了草房子的前四章,这四章分别有四个主要人物:秃鹤、纸月、白雀、秦大奶奶,还有一个核心人物——桑...
    梁琳茹阅读 205评论 0 1
  • 这个问题到今天仍然存在很多争议。 一方面,态度决定一切,从正向角度上讲,是在强调人要发挥主动性能动性,积极投入。这...
    壹壶茶阅读 214评论 0 0