Just be Pythonic ---Python PEP-8编码规范笔记 | Yet Another Thoughts 

JerryXia 发表于 , 阅读 (24)

PEP-8是由包括Guido van Rossum(Python语言创始人)在内的多名权威人士撰写的python编码规范。Python因为其简洁、接近自然语言的语法、强制缩进、高效的开发效率、一种方式做一种事情的特点成为了一门流行的动态语言,甚至专门有一个词汇——“pythonic”来形容一种优雅、典型的编码方式。那么,问题来了,如何be pythonic?来看看Guido怎么说。

Guido的看法之一是,代码被阅读的次数大于它被写的次数。像PEP-8这样的规范指南目的是提高代码的可读性,并且与Python代码的整体代码环境一致。编码指南在于一致性。与风格指南一致很重要,与项目代码一致更重要,一个模块与一个方法中的一致性更更重要。最困难的其实是,自己可以做决定,什么时候应该不考虑一致性,什么时候规范不适用。尤其是,不应当只是为了遵从PEP而破坏了向后兼容性。还有一些规则可以遵循,以下情况可以忽略编码规范:

  1. 当遵循了编码规范后代码可读性变差,甚至对习惯阅读符合编码规范的代码的时候。
  2. 可能因为历史原因,需要与项目其他的不符合规范的代码保持一致。同时,这也是一个清理其他人造成的一团糟的机会。
  3. 问题代码写在规范推出之前,并且没有理由修改那些代码。
  4. 代码需要保留与老版本python的兼容性同时老版本不支持编码规范中推荐的特性。

    代码布局

    缩进

    每个缩进层次使用4个空格。

隐式行续:继续的行应当与被各种括号包围的元素纵向对齐,或者使用悬挂式缩进。使用悬挂式缩进时,应当注意:第一行不该有参数,下面的缩进应当能够清晰地分辨出这是一个未完成的行。
Correct:

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) 

*Incorrect:

  foo = long_function_name(var_one, var_two,      var_three, var_four)def long_functin_name(    var_one, var_two, var_three,    var_four):    print(var_one)

多行中关闭的括号或者与最后一行的第一个非空格对齐,或者与开始多行的第一行第一个字符对齐。

tab还是空格?

优先考虑使用空格作为缩进方法。

在已经使用了tab作为缩进方法的代码中,为了一致性使用tab。

Python 3不允许混合使用tab和空格作为缩进方法。Python 2的代码不建议混合使用。

行最大长度

限制所有行的最大长度为79个字符。

对于那些具有很少的结构约束(例如文档字符串、注释)的代码段来说,最大行长度应该在在72个字符以内。

最为推荐的长行换行方式是在圆括号、方括号、花括号内的 Python 隐式行续。相比于使用反斜杠来转义续行,应该优先使用将长行放置于圆括号内来隐式续行的方式。某些时候反斜杠是适于使用的。

而在隐式换行时应当在适当位置进行。有二院操作符时最好在其之后而不是之前换行。

空白行

对于顶级的方法与类定义需要用两个空白行分隔。

使用一个空白行来分隔雷内的方法定义。

额外的空白行可以用于区分相关函数的分组。对于一系列的相关的单行的方法、定义可以忽略它们之间的空白行。

在方法定义内部使用空白行分隔逻辑上的分区。

Python 将 control-L (也就是, ^L) 换行符认作空白符。在许多工具中都将 control-L 识别做分页符,可以使用其来分页。但是注意,在某些编辑器或基于Web的代码浏览器中,control-L 是不会识别作换行符的,会被做为其他字符显示。

源文件编码

使用ASCII(py2中)或者UTF-8(py3中)不需要声明文件编码。

在 Python 3.0 与更高级的 Python 版本中,对 Python 标准库的源文件编码作出了如下规定:Python 标准库中的所有标示符必须仅使用 ACSII 编码的字符,在任何可能的时候都使用英文书写(在许多情况下,缩写名词和技术术语使用的是非英文)。另外,字符串文本与注释也必须使用 ASCII 编码。唯一的例外,是测试非 ASCII 编码特性的测试案例,与作者名的书写。对于非拉丁字符的作者名,应该将其翻译为拉丁字母书写。

Imports

Imports通常使用分开的多行:
Correct:

import osimport sys

Incorrect:

import sys, os

当然,这样是可以的:

from subprocess import Popen, PIPE

Imports应当总在文件的顶部,放在模块的comments和docstrings之后,模块全局变量或者常量声明之前。

Imports应该当按照以下顺序分组:

  1. 标准库imports
  2. 相关第三方库imports
  3. 当前应用/库的特定imports
    各个组之间应当用空白行分开。

建议使用绝对导入形式的import语句,更加易读,并且在配置错误有更好的import行为(至少更好的报错)。清晰的相对导入也是可以接收的,特别是当使用绝对导入需要处理不必要的复杂布局时。

Python标准库应该避免复杂的包布局,且总是使用绝对导入。
Py3的相对导入只能以.开头,任何非.开头的import都被认为是绝对路径import。

避免使用通配符import(from import *)。

字符串引号

坚持使用单引号或者双引号一种字符串引号的方式。当字符串中包含某一种引号时,使用另一种引号包裹字符串以避免转义,提高可读性。

对于三个引号的字符串,永远使用双引号。

表达式与语句中的空格

小问题

在以下情况下避免用空格:

  • 紧挨括号之后;
  • 逗号,分号,冒号之前;
  • 当在slice中,冒号成为一个二元操作符,同时两边有相同数目(冒号作为最低优先级的操作符)时不用空格。在扩展的slice中,冒号需要有相同数目的空格。当slice的参数省略了,空格也需要省略。
    Correct:
    ham[1:9], ham[1:9:3]
    ham[lower+offset : upper+offset], ham[lower + offset : upper + offset]
    ham[: upper]

  • 调用方法时在左括号前面;

  • 在indexing或者slicing的中括号之前;

其它的推荐规范

  • 赋值(=)、扩展赋值(+=,-=,etc)、比较(==,>,<,!=, in, not in, is, is not),布尔(and,or,not)总是要有空格包围。
  • 当操作符具有不同的优先级时,考虑只在最低优先级的操作符两关加空格。永远使用一个空格。
    Correct:

    hypot2 = x*x + y*y
    c = (a+b) * (a-b)

  • 在用作关键字参数或者默认参数值时=两边不要用空格。

  • 在方法定义中的annotation中的=两边要用空格,->两边也要用空格。
  • 不建议使用符合语句(在一个物理行中存在多条语句)。
  • 虽然有时把较短小的 if/for/while 语句放在同一物理行内也是可以的,但千万不要对多子句的语句也这样做,同时也是为了避免折叠长行。

注释

与代码冲突的注释还不如没注释的代码。更新代码同时更新注释是一个优先的任务。

注释应当是完整的句子。如果注释比较短,可以省略句号。

应当在句尾的句号后使用两个空格。

非英语程序员也应当使用英语写注释,除非你确定你的代码不会被说跟你不一样语言的人阅读。

块注释

块注释注释跟着它的代码,与它们使用相同的缩进。每一行使用#加一个空格开头。
块注释的段落用一行只有#的行隔开。

行内注释

尽量少用行内注释。

行内注释至少与代码隔开两个空格,#后跟一个空格。

文档字符串

PEP 257介绍了好的文档字符串(docstrings)的规范。

  • 对所有的公开模块、类、函数、方法撰写docstrings。Docstrings对非公开的 方法不太必要,但是应该在def声明后用注释描述这个方法是做什么的。
  • 最重要的是,结束docstrings的”””应当单独处于一行。
  • 对于单行的docstrings,”””都在同一行就可以。

版本注记

如果你使用了SVN,SVS,或者RCS,这样做版本注记:

__version__ = "$Reversion$"# $Source$

它应该被添加到docstrings之后,在任何其它代码之前,各用一个空白行包围。

命名规范

Python的库的命名比较混乱。而我们应当在新的模块、框架、包中遵循以下的规范。如ugoyig存在的库有着不一样的标准,内部的一致性很重要。

覆盖的原则

对用户可见的API公共部分的命名应该遵循优先反映其使用方法而不是实现方法。

描述性的:命名风格

你应该明白当前使用的是哪一种命名风格。

能被广泛地识别的是一下的命名风格:

  • b (一个小写字符)
  • B (一个大写字符)
  • lowercase
  • lower_case_with_underscores
  • UPPERCASE
  • UPPERCASE_WITH_UNDERSCORES
  • CapitaliedWords(or CapWords, or CamelCase)当使用缩写时大写所有的缩写,e.g. HTTPServerError
  • mixedCase(开头字母小写)
  • Capitalized_Words_With_Underscores(好丑)

还有种风格是使用短而独特的前缀来把相关的名字组合到一起,但在python中不是很常见。

在python中,以下划线开头的方法表示是约定的内部方法。

约定俗成的:命名规范

应当避免的名字

永远不要用’l’(lowercase el),’O’(uppercase letter oh), or ‘I’(uppercase eye) 作为单字符变量名。

包名与模块名

模块名应该简短,全部小写。如果可以提高可读性可以使用下划线。Python的包名也应当简短,全小写,但不建议使用下划线。

当使用C/C++写的扩展模块有一个相应的高层次的python模块接口,这个C/C++模块需要以下划线开头。

类名

类名应当适用驼峰命名法(CapWords)。

异常名字

采用跟类一样的命名规范。但应当以”Error”作为后缀(如果确实是一个error的话)。

###全局变量名字
这里嘉定这些全局变量只在当前模块使用。规范与方法的规范相同。

设计为可以通过from M import *使用的模块应当使用 __all__方法来防止暴露全局变量。或者使用约定俗成的前缀单下划线。

函数名

方法名应当是小写的,使用下划线隔开词以提高可读性。

混合方法只建议在要考虑向后兼容时使用。

函数与方法的参数

永远类实例方法第一个参数名是self,类方法第一个参数名是cls。

如果函数参数名誉一个保留字冲突了,尾部加一个下划线比使用缩写或者乱拼好。

方法名与实例变量

小写,必要时用下划线分隔词。

对于非公开的方法与实例变量用一个下划线开头。

为避免与子类命名冲突,使用两个下划线开头。

Python mangling rules: 如果类Foo有一个属性__a,你不可以这样获得它:foo.__a。当然你可以这样:foo._foo__a,被python内部处理了。一般,双下划线只应该在需要子类化扩展时为防止命名冲突时使用。

常量

模块级别的常量通常命名为全部大写,用下划线分隔词。

为继承而设计

下面是be pythonic一些准则:

  1. 公开的attribute不应该有下划线前缀。
  2. 如果你公开的attribute名与保留字冲突,尾随加一个下划线。
  3. 对于公开的简单的数据attribute,最好暴露其attribute名就好,无需复杂的accessor/mutator方法。Python提供了简单的方法为了未来的扩展。一个简单的数据attribute可以通过增加函数式的行为。这种情况下,用properties来隐藏函数式的实现到简单的数据attribute获取的语法之后。 但要注意要避免在计算密集的情况下使用properties。
  4. 如果你的类设计为要被子类化扩展,而对于那些不想被之类使用的attributes,考虑用双下划线开头来命名它们。

公开与内部的接口

任何向后兼容的保证都应该只适用于公开的接口。

文档过的接口应该被认为是公开的接口,否则是内部的接口。

模块中应当显式地使用__all__声明公开的API。

内部的接口仍然应该用单下划线开头。

导入的名字应当总被认为是实现细节。其它模块一定不要依赖非直接的方式得到这样导入的名字,除非它们被明确的在文档中表明是包含模块API的一部分。

编码的建议

  • 代码应该编写成对其他的python实现也足够友好(PyPy, Jython, IronPython,Cython, Psyco, 等)。
  • 与单例对象比如None的比较永远使用is, is not而不是等号。
  • 使用is not操作符而不是not … is。
  • 当实现丰富比较的排序操作符时,最好事先全部的留个操作符(__eq__, __ne__, __lt__, __le__, __gt__, __ge__)而不是依赖其它代码只实现一个特定的比较。 functools.total_ordering()提供了decorator来自动生成全部的比较操作符方法(被装饰的类需要提供eq()方法)。
  • 最好使用def绑定一个函数而不是直接绑定一个lambda表达式到一个标识符。

Recommanded:

def f(x): return 2 * x

Not recommended::

f = lambda x:2 * x

第一种形式包名结果的函数对象是’f’而不是通用的’‘,这样更容易追踪、表达更加友好。

  • 从Exception而不是BaseException来派生异常。直接从BaseException来派生保留到用于一类特殊的异常,这种异常被捕捉到总是错误的事情。
    设计异常的层次结构基于这样的认识,就是来捕捉异常的代码并不在异常被触发的地方,而是在这些代码被需要的地方。而异常最好携带能够分析错误原因的信息。PEP 3151– Reworking the OS and IO exception hierarchy是一个从内置异常层次结构学来的经验。
    对于那些非错误的异常,用于非local的流控制或者其它形式的信号处理,命名时并不需要特殊的后缀。
  • 适当地使用异常链(Exception Chaining)。在py3中,raise X from Y应当用于在不丢失原来的回溯路径情况下显式地显示替代。
    当有意地替代一个内部的exception(在py2中用”raise X”,或者在py3.3+中使用”raise X from None”),需要确保相关的细节被转移到新的异常中。
  • 在py2中,使用raise ValueError('message')而不是raise ValueError, 'message'。后者py3中是不允许的。
  • 在捕捉异常时,尽量使用越specific的exception越好。裸except连SystemExit或者KeyboardInterrupt异常都会捕捉到。如果你想要捕捉所有的说明程序错误的异常,使用except Exception
    一个使用裸except的两种情况是:

    1. 如果异常的处理这回被打印或者日志记录回溯路径;至少用户可以得知一个错误发生了。
    2. 如果代码需要做一些清理工作,但使用raise, try … finally是更好处理这种情况的方式。
  • 当绑定捕捉到的异常到一个名字上时,最好用python 2.6开始的语法,这个语法在py3中是唯一的语法:

try:        process_data()except Exception as exc:        raise DataProcessingFailedError(str(exc))
  • 当捕捉操作系统的错误时,最好用Python 3.3引入的显式的异常层次结构,而不是靠errno值上的约定俗成的方式。
  • 对于所有的try/except子句,必要情况下限制try子句到最小数目的代码,以避免隐藏bugs。

Recommended:

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

Not recommended:

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应当通过单独的方法或函数触发,只要它们会做除了获取或释放资源其它的事情。例如:

Recommended:

with conn.begin_transaction():        do_stuff_in_transaction(conn)

Not recommended:

with conn:      do_stuff_in_transaction(conn)

第二个例子并不会提供任何信息来表示__enter__与__exit__方法会除了在transaction之后除了关闭connection做其他的事情。

  • return语句要一致。一个函数要么都会返回一个表达式或者都不要。
  • 尽量使用字符串的方法而不是string模块。字符串方法总是很快,同时与unicode字符串共享相同的API。
  • 使用.startswith().endswith()代替字符串slicing来查前缀、后缀。
  • 对象的类型比较永远用isinstance()而不是直接比较类型。在py2中,不要忘了还有unicode字符串,str与unicode字符串有一个共同的基类——basestring。
  • 对于序列(strings,lists,tuples),空的序列是False。
  • 对于字符串字面量不要尾随空格。
  • 不要将布尔值与True与False用==比较。
  • Python的标准库并没有使用函数annotations,这样的话会造成形成不成熟的规范。

最后

最后,提一下,大公司都有内部的编码规范,有些公司已经开放了出来,比如Google Python Style Guide