PEP 8 - Python代码样式指南

内容

介绍

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

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

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

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

愚蠢的一致性是小思想的大人物

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

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

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

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

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

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

代码布局

缩进

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

延续线应使用Python的隐含线连接在括号,括号和大括号内,或使用悬挂缩进 [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):
    打印(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):
    打印(var_one)

对于连续线,4空间规则是可选的。

可选的:

#悬挂缩进*可以*缩进到4个空格以外的其他位置。
foo = long_function_name(
  var_one,var_two,
  var_three,var_four)

if语句的条件部分足够长以要求它跨多行写入时,值得注意的是两个字符关键字(即if)的组合,加上单个空格,加上一个左括号创建一个自然的多行条件的后续行的4空格缩进。这可能与嵌套在if -statement中的缩进代码集产生视觉冲突,该代码集自然也会缩进到4个空格。该PEP没有明确地说明如何(或是否)进一步在视觉上将这些条件线与if-statement 内的嵌套套件区分开来在这种情况下可接受的选择包括但不限于:

#没有额外的缩进。
if(this_is_one_thing和
    that_is_another_thing):
    做一点事()

#添加注释,这将在编辑器中提供一些区别
#支持语法高亮显示。
if(this_is_one_thing和
    that_is_another_thing):
    #由于这两个条件都属实,我们可以讨厌。
    做一点事()

#在条件连续行上添加一些额外的缩进。
if(this_is_one_thing
        和that_is_another_thing):
    做一点事()

(另请参阅下面关于是否在二元运算符之前或之后中断的讨论。)

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

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,multiple with -statements不能使用隐式延续,因此可以接受反斜杠:

打开('/ path / to / some / file / you / want / to / read')as file_1,\
     打开('/ path / to / some / file / being / written','w')作为file_2:
    file_2.write(file_1.read())

(参见前面关于多行if语句的讨论,以进一步思考这种带有 -statements的多行缩进。)

另一个这样的情况是使用断言语句。

确保适当地缩进续行。

应该在二元运算符之前或之后换行吗?

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

#No:运营商远离他们的操作数
收入=(gross_wages +
          taxable_interest +
          (股息 -  qualified_dividends) - 
          ira_deduction  - 
          student_loan_interest)

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

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

#Yis:易于将操作符与操作数匹配
收入=(gross_wages
          + taxable_interest
          +(股息 -  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格式。唯一的例外是(a)测试非ASCII功能的测试用例,以及(b)作者姓名。名称不是基于拉丁字母(latin-1,ISO / IEC 8859-1字符集)的作者必须在此字符集中提供其名称的音译。

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

进口

  • 进口通常应分开:

    是的:导入操作系统
         导入系统
    
    No:import sys,os
    

    可以这样说:

    来自子进程导入Popen,PIPE
    
  • 导入总是放在文件的顶部,就在任何模块注释和文档字符串之后,以及模块全局变量和常量之前。

    应按以下顺序对导入进行分组:

    1. 标准库导入。
    2. 相关的第三方进口。
    3. 本地应用程序/库特定导入。

    您应该在每组导入之间添加一个空行。

  • 建议使用绝对导入,因为如果导入系统配置不正确(例如,当包中的目录最终出现在sys.path上时,它们通常更具可读性并且往往表现更好(或至少提供更好的错误消息):

    导入mypkg.sibling
    来自mypkg import sibling
    来自mypkg.sibling导入示例
    

    但是,显式相对导入是绝对导入的可接受替代方法,尤其是在处理复杂的包布局时,使用绝对导入会不必要地冗长:

    来自。进口兄弟
    来自.sibling导入示例
    

    标准库代码应避免复杂的包布局,并始终使用绝对导入。

    永远不应该使用隐式相对导入,并且已经在Python 3中删除了。

  • 从包含类的模块导入类时,通常可以拼写:

    从myclass导入MyClass
    来自foo.bar.yourclass导入YourClass
    

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

    导入myclass
    import foo.bar.yourclass
    

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

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

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

模块级别Dunder名称

模块级“dunders”(即名称具有两个前缘和两个纵下划线)如__all____author____version__等应被放置在模块文档字符串之后,但在任何导入语句以外 从__future__进口。Python要求future-imports必须在除docstrings之外的任何其他代码之前出现在模块中:

“”这是示例模块。

这个模块做的东西。
“””

来自__future__ import barry_as_FLUFL

__all__ = ['a','b','c']
__version__ ='0.1'
__author__ ='红衣主教Biggles'

进口口
导入系统

字符串引号

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

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

表达式和语句中的空格

宠物皮皮鬼

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

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

    是的:垃圾邮件(火腿[1],{蛋:2})
    不:垃圾邮件(火腿[1],{蛋:2})
    
  • 在尾随逗号和后续括号之间。

    是的:foo =(0,)
    No:bar =(0,)
    
  • 在逗号,分号或冒号之前:

    是:如果x == 4:打印x,y; x,y = y,x
    否:如果x == 4:打印x,y; x,y = y,x
    
  • 但是,在切片中,冒号的行为类似于二元运算符,并且两侧的数量应该相等(将其视为具有最低优先级的运算符)。在扩展切片中,两个冒号必须具有相同的间距。例外:省略slice参数时,省略空格。

    是:

    火腿[1:9],火腿[1:9:3],火腿[:9:3],火腿[1 :: 3],火腿[1:9:]
    火腿[lower:upper],火腿[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]
    火腿[1:9],火腿[1:9],火腿[1:9:3]
    火腿[lower :: upper]
    火腿[:上]
    
  • 紧接在启动函数调用的参数列表的左括号之前:

    是的:垃圾邮件(1)
    否:垃圾邮件(1)
    
  • 紧接在开始索引或切片的左括号之前:

    是的:dct ['key'] = lst [index]
    否:dct ['key'] = lst [index]
    
  • 赋值(或其他)运算符周围有多个空格,以使其与另一个运算符对齐。

    是:

    x = 1
    y = 2
    long_variable = 3
    

    没有:

    x = 1
    y = 2
    long_variable = 3
    

其他建议

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

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

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

    是:

    i = i + 1
    提交+ = 1
    x = x * 2  -  1
    hypot2 = x * x + y * y
    c =(a + b)*(ab)
    

    没有:

    I = I + 1
    提交+ = 1
    x = x * 2  -  1
    hypot2 = x * x + y * y
    c =(a + b)*(a  -  b)
    
  • 函数注释应该使用冒号的常规规则,并且如果存在,则始终在- >箭头周围留出空格(有关函数注释的更多信息,请参阅 下面的函数注释。)

    是:

    def munge(输入:AnyStr):...
    def munge() - > AnyStr:...
    

    没有:

    def munge(输入:AnyStr):...
    def munge() - > PosInt:...
    
  • 当用于指示关键字参数时,或者用于指示未注释函数参数的默认值时,请勿在=符号 周围使用空格

    是:

    def complex(real,imag = 0.0):
        返回魔法(r =真实,i =成像)
    

    没有:

    def complex(real,imag = 0.0):
        返回魔法(r =真实,i =成像)
    

    但是,在将参数注释与默认值组合时,请使用=符号周围的空格

    是:

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

    没有:

    def munge(输入:AnyStr =无):...
    def munge(输入:AnyStr,limit = 1000):...
    
  • 通常不鼓励使用复合语句(同一行上的多个语句)。

    是:

    如果foo =='blah':
        do_blah_thing()
    do_one()
    do_two()
    do_three()
    

    而不是:

    如果foo =='blah':do_blah_thing()
    do_one(); do_two(); do_three()
    
  • 虽然有时可以在同一行上放置一个带有小体的if / for / while,但是不要对多子句语句执行此操作。还要避免折叠如此长的线条!

    而不是:

    如果foo =='blah':do_blah_thing()
    for l in lst:total + = x
    而t <10:t =延迟()
    

    当然不:

    如果foo =='blah':do_blah_thing()
    else:do_non_blah_thing()
    
    尝试:某事()
    终于:清理()
    
    do_one(); do_two(); do_three(长,争论,
                                 列表,像,这个)
    
    if foo =='blah':one(); 二(); 三()
    

何时使用尾随逗号

尾随逗号通常是可选的,除了在创建一个元素的元组时它们是必需的(在Python 2中它们具有print语句的语义)。为清楚起见,建议将后者括在(技术上冗余的)括号中。

是:

FILES =('setup.cfg',)

好的,但令人困惑:

FILES ='setup.cfg',

当尾随逗号是多余的时,它们通常在使用版本控制系统时有用,当预期值列表,参数或导入项目随时间延长时。模式是将每个值(等)单独放在一行上,始终添加一个尾随逗号,并在下一行添加右括号/括号/括号。但是,在与结束分隔符相同的行上使用尾随逗号是没有意义的(除了上面的单例元组的情况)。

是:

文件= [
    'setup.cfg',
    'tox.ini',
    ]
初始化(文件,
           错误=真,

没有:

FILES = ['setup.cfg','tox.ini',]
初始化(FILES,错误= True,)

评论

与代码相矛盾的评论比没有评论更糟糕。始终优先考虑在代码更改时保持评论的最新状态!

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

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

除了最后一句之外,你应该在句子结束时间之后的多句话评论中使用两个空格。

写英文时,请遵循Strunk和White。

来自非英语国家的Python程序员:请用英语撰写您的评论,除非您确信不会说不懂您语言的人不会阅读该代码。

阻止评论

块注释通常适用于跟随它们的一些(或所有)代码,并且缩进到与该代码相同的级别。块注释的每一行都以和单个空格开头(除非它是注释中的缩进文本)。

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

内联评论

谨慎使用内联评论。

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

内联注释是不必要的,如果他们说明显的话,实际上会分散注意力。不要这样做:

x = x + 1#增量x

但有时,这很有用:

x = x + 1#补偿边界

文档字符串

编写好的文档字符串(又名“docstrings”)的约定在PEP 257中是永生的

  • 为所有公共模块,函数,类和方法编写文档字符串。对于非公共方法,文档字符串不是必需的,但是您应该有一个注释来描述该方法的作用。此评论应出现在def行之后。

  • PEP 257描述了良好的文档字符串约定。请注意,最重要的是,结束多行文档字符串“”“应该在一行上:

    “”返回一个foobang
    
    可选的plotz说要首先对bizbaz进行欺骗。
    “””
    
  • 对于一个班轮文档字符串,请保持关闭“”“在同一行。

命名约定

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

压倒一切的原则

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

描述性:命名样式

有很多不同的命名方式。它有助于识别正在使用的命名样式,与它们的用途无关。

通常会区分以下命名样式:

  • b(单个小写字母)

  • B(单个大写字母)

  • 小写

  • lower_case_with_underscores

  • 大写

  • UPPER_CASE_WITH_UNDERSCORES

  • 写词(或CapWords,或CamelCase - 因其字母凹凸不平而得名[4])。这有时也被称为StudlyCaps。

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

  • mixedCase(与CapitalizedWords的不同之处在于初始小写字符!)

  • Capitalized_Words_With_Underscores(丑陋!)

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

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

此外,还会识别使用前导或尾随下划线的以下特殊形式(这些形式通常可与任何案例约定结合使用):

  • _single_leading_underscore:弱“内部使用”指标。例如,来自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'(小写字母el),'O'(大写字母哦)或'I'(大写字母眼睛)用作单个字符变量名称。

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

ASCII兼容性

如描述的标准库使用的标识符必须是ASCII兼容 政策课 的PEP 3131

包和模块名称

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

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

班级名称

类名通常应使用CapWords约定。

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

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

输入变量名称

PEP 484中引入的类型变量的名称通常应使用CapWords ,而不是短名称:TAnyStrNum建议将后缀_co_contra添加到用于相应声明协变或逆变行为的变量中:

从键入导入TypeVar

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

例外名称

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

全局变量名称

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

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

函数和变量名称

函数名称应为小写,并根据需要用下划线分隔,以提高可读性。

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

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

函数和方法参数

始终使用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不是,而不是相等的运算符。

    另外,当你真正意味着x不是None,要小心写x例如,当测试默认为None的变量或参数是否设置为其他值时。另一个值可能有一个在布尔上下文中可能为false的类型(如容器)!

  • 使用不是运营商而不是......是虽然两个表达式在功能上是相同的,但前者更具可读性和首选性。

    是:

    如果foo不是None:
    

    没有:

    如果不是foo是None:
    
  • 当实现与比较丰富排序操作,最好是实现所有六个操作(__eq____ne__, __lt____le____gt____ge__)而不是依靠其他代码,只行使特定的比较。

    为了最大限度地减少所涉及的工作量,functools.total_ordering() 装饰器提供了一个生成缺失比较方法的工具。

    PEP 207表明,反身性的规则由Python的假设。因此,解释器可以将y> xx <yy> = x交换, 其中x <= y,并且可以交换x == yx!= y的参数排序()MIN()操作可保证使用<运算符和MAX()函数使用> 运算符。但是,最好实施所有六个操作,以免在其他情况下出现混淆。

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

    是:

    def f(x):返回2 * x
    

    没有:

    f = lambda x:2 * x
    

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

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

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

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

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

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

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

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

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

  • 捕获异常时,请尽可能提及特定异常,而不是使用bare except:子句:

    尝试:
        import platform_specific_module
    除了ImportError:
        platform_specific_module =无
    

    一个裸的except:子句将捕获SystemExit和KeyboardInterrupt异常,这使得用Control-C中断程序变得更加困难,并且可以掩盖其他问题。如果要捕获发出程序错误信号的所有异常,请使用 除Exception :(裸除了除了BaseException之外)。

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

    1. 如果异常处理程序将打印出来或记录回溯; 至少用户会意识到发生了错误。
    2. 如果代码需要做一些清理工作,但随后让异常向上传播并加注。 尝试...终于 可以更好地处理这种情况。
  • 绑定捕获的名称异常时,更喜欢Python 2.6中添加的显式名称绑定语法:

    尝试:
        处理数据()
    除了异常为exc:
        引发DataProcessingFailedError(str(exc))
    

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

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

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

    是:

    尝试:
        value =集合[关键]
    除了KeyError:
        return key_not_found(key)
    其他:
        return handle_value(value)
    

    没有:

    尝试:
        # 太宽泛!
        return handle_value(collection [key])
    除了KeyError:
        #还会捕获handle_value()引发的KeyError
        return key_not_found(key)
    
  • 当资源是特定代码段的本地资源时,请使用 with语句以确保在使用后立即可靠地清除它。try / finally语句也是可以接受的。

  • 除了获取和释放资源之外,还应该通过单独的函数或方法调用上下文管理器。

    是:

    与conn.begin_transaction():
        do_stuff_in_transaction(康涅狄格州)
    

    没有:

    与conn:
        do_stuff_in_transaction(康涅狄格州)
    

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

  • 在回报陈述中保持一致。函数中的所有return语句都应该返回一个表达式,或者它们都不应该返回。如果任何return语句返回一个表达式,那么任何没有返回值的return语句都应该明确地将其声明return None,并且在函数末尾应该有一个显式的return语句(如果可以访问)。

    是:

    def foo(x):
        如果x> = 0:
            return math.sqrt(x)
        其他:
            返回无
    
    def bar(x):
        如果x <0:
            返回无
        return math.sqrt(x)
    

    没有:

    def foo(x):
        如果x> = 0:
            return math.sqrt(x)
    
    def bar(x):
        如果x <0:
            返回
        return math.sqrt(x)
    
  • 使用字符串方法而不是字符串模块。

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

  • 使用'.startswith()''。endswith()而不是字符串切片来检查前缀或后缀。

    startswith()和endswith()更清晰,更不容易出错:

    是的:如果foo.startswith('bar'):
    不:如果foo [:3] =='bar':
    
  • 对象类型比较应始终使用isinstance()而不是直接比较类型。

    是:如果isinstance(obj,int):
    
    否:如果type(obj)是type(1):
    

    检查对象是否为字符串时,请记住它也可能是一个unicode字符串!在Python 2中,str和unicode有一个共同的基类,basetring,所以你可以这样做:

    if isinstance(obj,basestring):
    

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

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

    是的:如果不是seq:
         如果seq:
    
    否:如果len(seq):
         如果不是len(seq):
    
  • 不要编写依赖于重要尾随空格的字符串文字。这样的尾随空白在视觉上是难以区分的,一些编辑器(或者最近,reindent.py)将修剪它们。

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

    是的:如果问候:
    否:如果问候==真:
    更糟糕的是:如果问候是真的:
    

功能注释

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

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

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

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

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

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

    #type:忽略
    

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

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

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

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

变量注释

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

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

  • 结肠前应该没有空格。

  • 如果赋值具有右侧,则等号在两侧应该只有一个空格。

  • 是:

    代码:int
    
    课程要点:
        coords:元组[int,int]
        label:str ='<unknown>'
    
  • 没有:

    code:int#冒号后没有空格
    code:int#冒号前的空格
    
    班级考试:
        result:int = 0#等号周围没有空格
    
  • 尽管PEP 526被Python 3.6接受,但变量注释语法是所有Python版本的存根文件的首选语法(有关详细信息,请参阅PEP 484)。

脚注

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

 

posted @ 2018-12-15 17:40  小萍瓶盖儿  阅读(547)  评论(0编辑  收藏  举报