Python

14.PEP8代码风格指南

2020-03-15  本文已影响0人  小哲1998

不要为了遵守这份风格指南而破坏代码的向后兼容性。


这里有一些好的理由去忽略某个风格指南:

  1. 当应用风格指南的时候使代码更难读了,对于严格依循风格指南的约定去读代码的人也是不应该的。
  2. 为了保持和风格指南的一致性同时也打破了现有代码的一致性(可能是历史原因)–虽然这也是一个整理混乱代码的机会(现实中的 XP 风格)。
  3. 因为问题代码的历史比较久远,修改代码就没有必要性了。
  4. 当代码需要与旧版本的 Python 保持兼容,而旧版 Python 又不支持风格指南中提到的特性的时候。

1.代码排版

1.1 缩进

例如:


代码缩进
1.2 制表符还是空格?
1.3 每行最大长度

限制每行的最大长度为79个字符。对于那些约束很少的文本结构(文档字符串或注释)的长块,应该限制每行长度为72个字符。限制编辑窗口的宽度使并排打开两个窗口成为可能,使用通过代码审查工具时,也能很好的通过相邻列展现不同代码版本。一些工具的默认换行设置打乱了代码的可视结构,使其更难理解。限制编辑器窗口宽为80来避免自动换行,即使有些编辑工具在换行的时候会在最后一列放一个标识符。一些基于Web的工具可能根本就不提供动态换行。一些团队更倾向于长的代码行。对于达成了一致意见来统一代码的团队而言,把行提升到80~100的长度是可接受的(实际最大长度为99个字符),注释和文档字符串的长度还是建议在72个字符内。Python 标准库是非常专业的,限制最大代码长度为79个字符(注释和文档字符串最大长度为72个字符)。首选的换行方式是在括号(小中大)内隐式换行(非续行符\)。长行应该在括号表达式的包裹下换行。这比反斜杠作为续行符更好。反斜杠有时仍然适用。例如,多个很长的 with语句不能使用隐式续行,因此反斜杠是可接受的。

反斜杠换行

另一种使用反斜杠续行的案例是assert语句。确保续行的缩进是恰到好处的。遇到二元操作符,首选的断行位置是操作符的后面而不是前面。

1.4 空行
1.5 源文件编码
1.6 导入包

import不同的模块应该独立一行:


导入包

每组之间应该用空行分开。然后用__all__声明本文件内的模块。
绝对导入是推荐的,它们通常是更可读的,并且在错误的包系统配置(如一个目录包含一个以os.path结尾的包)下有良好的行为倾向(至少有更清晰的错误消息):

绝对定位

标准库代码应该避免复杂的包结构,并且永远使用绝对导入。应该从不使用隐式的相对导入,而且在 Python 3 中已经被移除。

应该避免通配符导入(from import *),这会使名称空间里存在的名称变得不清晰,迷惑读者和自动化工具。这里有一个可辩护的通配符导入用例,,重新发布一个内部接口作为公共 API 的一部分(例如,使用纯 Python 实现一个可选的加速器模块的接口,但并不能预知这些定义会被覆盖)。

2.字符串引号

在 Python 里面,单引号字符串和双引号字符串是相同的。这份指南对这个不会有所建议。选择一种方式并坚持使用。一个字符串同时包含单引号和双引号字符时,用另外一种来包裹字符串,而不是使用反斜杠来转义,以提高可读性。

3.表达式和语句中的空格

避免在下列情况中使用多余的空格:

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
切片
Yes: spam(1)
No:  spam (1)
Yes: dct['key'] = lst[index]
No:  dct ['key'] = lst [index]

其他建议:

操作符
def complex(real, imag = 0.0):
    return magic(r = real, i = imag)  # r=real, i=img是差的写法
def munge(input: AnyStr):
def munge(sep: AnyStr = None):
def munge() -> AnyStr: # def munge()->AnyStr:是差的写法
def munge(input: AnyStr, sep: AnyStr = None, limit=1000):

4.注释

与代码相矛盾的注释不如没有。注释总是随着代码的变更而更新。注释应该是完整的句子。如果注释是一个短语或语句,第一个单词应该大写,除非是一个开头是小写的标识符(从不改变标识符的大小写)。如果注释很短,末尾的句点可以省略。块注释通常由一个或多个有完整句子的段落组成,并且每个句子应该由句点结束。你应该在一个句子的句点后面用两个空格。


文档字符串:

"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.
"""

5.版本注记

如果必须要 Subversion,CVS 或 RCS 标记在你的源文件里,像这样做:

__version__ = "`$Revision$`"
# `$Source$`

这几行应该在模块的文档字符串后面,其它代码的前面,上下由一个空行分隔。

6.命名约定

Python 库的命名规则有点混乱,因此我们永远也不会使其完全一致的 – 不过,这里有一些当前推荐的命名标准。新的模块和包(包括第三方框架)应该按照这些标准来命名,但是已存在库有不同的风格,内部一致性是首选。

6.1 覆盖原则

API 里对用户可见的公共部分应该遵循约定,反映的是使用而不是实现。

6.2 规定:命名约定

有许多不同的命名风格。这有助于识别正在使用的命名风格,独立于它们的用途。

下面的命名风格通常是有区别的:

X11 库的所有公共函数都用 X 打头。在 Python 中这种风格被认为是不重要的,因为属性和方法名的前缀是一个对象,函数名的前缀为一个模块名。

此外,下面的特许形式用一个前导或尾随的下划线进行识别(这些通常可以和任何形式的命名约定组合):

6.3 规定:命名约定

6.3.1 应该避免的名字
永远不要使用单个字符l(小写字母 el),O(大写字母 oh),或I(大写字母 eye)作为变量名。在一些字体中,这些字符是无法和数字10区分开的。试图使用l时用L代替。

6.3.2 包和模块名
模块名应该短,且全小写。如果能改善可读性,可以使用下划线。Python 的包名也应该短,全部小写,但是不推荐使用下划线。因为模块名就是文件名,而一些文件系统是大小写不敏感的,并且自动截断长文件名,所以给模块名取一个短小的名字是非常重要的 – 在 Unix 上这不是问题,但是把代码放到老版本的 Mac, Windows,或者 DOS 上就可能变成一个问题了。用 C/C++ 给 Python 写一个高性能的扩展(e.g. more object oriented)接口的时候,C/C++ 模块名应该有一个前导下划线。

6.3.2 类名
类名通常使用 CapWords 约定。
The naming convention for functions may be used instead in cases where the interface is documented and used primarily as a callable.
注意和内建名称的区分开:大多数内建名称是一个单独的单词(或两个单词一起),CapWords 约定只被用在异常名和内建常量上。

6.3.4 异常名
因为异常应该是类,所以类名约定在这里适用。但是,你应该用Error作为你的异常名的后缀(异常实际上是一个错误)。

6.3.5 全局变量名
(我们希望这些变量仅仅在一个模块内部使用)这个约定有关诸如此类的变量。若被设计的模块可以通过from M import *来使用,它应该使用__all__机制来表明那些可以可导出的全局变量,或者使用下划线前缀的全局变量表明其是模块私有的。

6.3.6 函数名
函数名应该是小写的,有必要的话用下划线来分隔单词提高可读性。混合大小写仅仅在上下文都是这种风格的情况下允许存在(如thread.py),这是为了维持向后兼容性。

6.3.7 函数和方法参数
总是使用self作为实例方法的第一个参数。
总是使用cls作为类方法的第一个参数。
如果函数参数与保留关键字冲突,通常最好在参数后面添加一个尾随的下划线,而不是使用缩写或胡乱拆减。因此class_clss要好。(或许避免冲突更好的方式是使用近义词)

6.3.8 方法名和实例变量
用函数名的命名规则:全部小写,用下划线分隔单词提高可读性。
用一个且有一个前导的下划线来表明非公有的方法和实例变量。
为了避免与子类变量或方法的命名冲突,用两个前导下划线来调用 Python 的命名改编规则。
Python 命名改编通过添加一个类名:如果类Foo有一个属性叫__a,它不能被这样Foo.__a访问(执着的人可以通过这样Foo._Foo__a来访问)通常,双前导的下划线应该仅仅用来避免与其子类属性的命名冲突。
注意:这里有一些争议有关__names的使用(见下文)。

6.3.9 常量
常量通常是模块级的定义,全部大写,单词之间以下划线分隔。例如MAX_OVERFLOWTOTAL

6.3.10 继承的设计
总是决定一个类的方法和变量(属性)是应该公有还是非公有。如果有疑问,选择非公有;相比把共有属性变非公有,非公有属性变公有会容易得多。
公有属性是你期望给那些与你的类无关的客户端使用的,你应该保证不会出现不向后兼容的改变。非公有的属性是你不打算给其它第三方使用的;你不需要保证非公有的属性不会改变甚至被移除也是可以的。
我们这里不适用“私有”这个术语,因为在 Python 里没有真正的私有属性(一般没有不必要的工作量)。
另一种属性的分类是“子类 API”的一部分(通常在其它语言里叫做“Protected”)。一些类被设计成被继承的,要么扩展要么修改类的某方面行为。设计这样一个类的时候,务必做出明确的决定,哪些是公有的,其将会成为子类 API 的一部分,哪些仅仅是用于你的基类的。
处于这种考虑,给出 Pythonic 的指南:

7.公共和内部接口

保证所有公有接口的向后兼容性。用户能清晰的区分公有和内部接口是重要的。文档化的接口考虑公有,除非文档明确的说明它们是暂时的,或者内部接口不保证其的向后兼容性。所有的非文档化的应该被假设为非公开的。为了更好的支持内省,模块应该用__all__属性来明确规定公有 API 的名字。设置__all__为空list表明模块没有公有 API。甚至与__all__设置相当,内部接口(包、模块、类、函数、属性或者其它的名字)应该有一个前导的下划线前缀。被认为是内部的接口,其包含的任何名称空间(包、模块或类)也被认为是内部的。导入的名称应始终视作一个实现细节。其它模块不能依赖间接访问这些导入的名字,除非它们是包含模块的 API 明确记载的一部分,例如os.path或一个包的__init__模块暴露了来自子模块的功能。

8.程序编写建议

好的:

if foo is not None:

不好的:

if not foo is None:

好的:

def f(x): return 2*x

不好的:

f = lambda x: 2*x

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

例如,用:

try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None

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

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

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

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

好的:

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 conn.begin_transaction():
    do_stuff_in_transaction(conn)

不好的:

with conn:
    do_stuff_in_transaction(conn)

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

好的:

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。如果需要向后兼容性覆盖这个规则,需要 Python 2.0 以上的版本。

Yes: if foo.startswith('bar'):
No:  if foo[:3] == 'bar':
Yes: if isinstance(obj, int):

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

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

if isinstance(obj, basestring):

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

好的:

if not seq:
if seq:

不好的:

if len(seq):
if not len(seq):
Yes:   if greeting:
No:    if greeting == True:
Worse: if greeting is True:

参考文献
[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]:http://www.wikipedia.com/wiki/CamelCase

版权说明
This document has been placed in the public domain.
Source: https://hg.python.org/peps/file/tip/pep-0008.txt


上一篇下一篇

猜你喜欢

热点阅读