PEP8中文版 -- Python编码风格指南(下)

Python部落组织翻译, 禁止转载

继承的设计


确定类的方法和实例变量(统称为:“属性”)是否公开。如果有疑问,选择非公开;之后把其变成公开比把一个公开属性改成非公开要容易。

公开属性是那些你期望与你的类不相关的客户使用的,根据你的承诺来避免向后不兼容的变更。非公开属性是那些不打算被第三方使用的;你不保证非公开属性不会改变甚至被删除。

非公共属性是那些不打算被第三方使用的,你不能保证非公开的属性不会改变甚至被删除。

此处没有使用术语“private”,因为Python中没有真正私有的属性(没有通常的不必要的工作)。

属性的另一个类别是“API子集”的一部分(在其它语言常被称为“protected”)。某些类被设计为基类,要么扩展,要么修改某些方面的类的行为。在设计这样的类的时候,要注意明确哪些属性是公开的,哪些是API子类的一部分,哪些是真正只在你的基类中使用。


清楚这些之后,这是Python特色的指南:

  • 公开属性没有前导下划线。

  • 如果公开属性名和保留关键字冲突,给属性名添加一个后置下划线。这比缩写或错误拼写更可取。(然而,尽管有这样的规定,对于任何类的变量或参数,特别是类方法的第一个参数,‘cls’是首选的拼写方式)

           注1:参见上面对类方法的参数名的建议。

  • 对于简单的公开数据属性,最好只暴露属性名,没有复杂的访问器/修改器方法。记住,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的高效实现字符串连接的语句形式 += b或a = a + b。这种优化即使在CPython里也是脆弱的(它只适用于某些类型),并且在不使用引用计数的实现中它完全不存在。在库的性能易受影响的部分,应使用''.join()形式。这将确保跨越不同实现的连接发生在线性时间。


与单值比如None比较使用is 或is not ,不要用等号操作符。


同样,如果你真正的意思是if x is not None,谨防编写if x。例如,当测试一个默认是None的变量或参数是否被置成其它的值时。这个其它值可能是在布尔上下文为假的类型(例如容器)。


使用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 > x和x < y,y >= x和x <= y,也可以交换参数x == y和x != y。sort()和min()操作保证使用< 操作符并且max()功能使用> 操作符。不管怎样,最好实现所有六个操作,这样在其它上下文就不会产生混淆了。


使用def语句而不是使用赋值语句将lambda表达式绑定到标识符上。

风格良好:
def f(x): return 2*x
风格不良:
f = lambda x: 2*x

第一种形式意味着所得的函数对象的名称是‘f’而不是一般的‘<lambda>’。这在回溯和字符串表示中更有用。赋值语句的使用消除了lambda表达式可以提供显示def声明的唯一好处(例如它可以嵌在更大的表达式里面)。


从Exception而不是BaseException中派生出异常。直接继承BaseException是保留那些捕捉几乎总是错的异常的。


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


类的命名规则适用于此,只是当异常确实是错误的时候,需要在异常类名添加“Error”后缀。用于非本地的流控制或其他形式的信号的非错误的异常,不需要特殊的后缀。


适当使用异常链。Python3中,“raise X from Y”用来表明明确的更换而不失去原来追踪到的信息。


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


Python 3中产生异常,使用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 BaseException:)
一条好的经验法则是限制使用空‘except’子句的两种情况:

       1.如果异常处理程序将打印或记录跟踪;至少用户将会意识到有错误发生。
       2.如果代码需要做一些清理工作,但是随后让异常用raise抛出。处理这种情况用try...finally更好。

当用一个名字绑定捕获异常时,更喜欢Python2.6中添加的明确的名称绑定语法。

try:
    process_data()
except Exception as exc:
    raise DataProcessingFailedError(str(exc))

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

捕获操作系统异常时,更喜欢Python 3.3引进的明确的异常层次,通过errno值自省。

另外,对于所有的try/except子句,限制try子句到必须的绝对最少代码量。这避免掩盖错误。

风格良好:
try:
    value = collection[key]
except KeyError:
    return key_not_found(key)
else:
    return handle_value(value)
风格不良:
try:
    # 很多代码
    return handle_value(collection[key])
except KeyError:
    # 捕获由handle_value()抛出的KeyError
    return key_not_found(key)

当一个资源是一个局部特定的代码段,它使用后用with语句确保迅速可靠的将它清理掉。也可以使用try/finally语句。
无论何时做了除了获取或释放资源的一些操作,应该通过单独的函数或方法调用上下文管理器。例如:

风格良好:
with conn.begin_transaction():
    do_stuff_in_transaction(conn)
风格不良:
with conn:
    do_stuff_in_transaction(conn)

后者的例子没有提供任何信息表明__enter__和__exit__方法做了什么除了事务结束后关闭连接。 在这种情况下,明确是很重要的。
返回语句保持一致。函数中的所有返回语句都有返回值,或都没有返回值。如果任意一个返回语句有返回值,那么任意没有返回值的返回语句应该明确指出return None,并且明确返回语句应该放在函数结尾(如果可以)。

风格良好:
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)

使用字符串方法代替string模块。

字符串方法总是更快并且与unicode字符串使用相同的API。如果必须向后兼容Python2.0以前的版本,无视这个原则。
使用“.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):

注意,Python3中, unicode和basestring不再存在(只有str),并且字节对象不再是string(而是一个integers序列)
对于序列(字符串,列表,元组),利用空序列是false的事实。

风格良好: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:

Python标准库不使用函数注解,由于它将给一个特定的注释风格造成过早的承诺。相反,注释是留给用户去发现和试验的有用的注释样式。


建议第三方实验注释使用一个相关的修饰符表明解释器如果解释注解来试验注解。


早期的核心开发人员尝试使用显示不一致的函数注解,特别的注释风格。例如:

[str]是模糊的,它是代表一个字符串列表还是一个可以是str或None的值。


符号open(file:(str,bytes))被用于值是 bytes或str替代包含一个str紧跟一个bytes值的二元元组。


seek(whence:int)注解展示出了一个规定外和规范内的混合:int限制太多(任何与__index__将被允许),它还不够严格(只有值0,1,和2是允许的)。同样的,write(b:bytes)注解也有太多限制(任何支持缓冲协议的将被允许)。


有些注解如read1(n:int=None)是自相矛盾的因为None不是一个int值。有些注解如assource_path(self,fullname:str) -> object,令人困惑它的返回类型是什么。


除了以上的,注解在具体类型和抽象类型的使用上是不一致的:int与Integral相对,set/frozenset与MutableSet/Set相对。
抽象基类的一些注解是不正确的规范。例如,set-to-set操作需要Other是另一个Set的实例而不是一个Iterable。

另一个问题是注解成为规范的一部分,但它没有被测试。


在大多数情况下,文档字符串已经包含了类型规范,并且比函数注解更清晰。在其余的情况下,一旦注解被移除文档字符串将改进。
所见到的函数注解太特别而且与一个自动类型检查或参数验证的连贯系统不一致。在代码中留下这些注解将使它以后更难做出改变以便自动化工具支持。

脚注


[5]悬挂缩进是段落中除第一行之外其它所有行都缩进的格式。在Python中,这个术语用来描述左括号括起来的语句,这行的最后非空白的字符,随后行缩进,直到右括号。


参考


[1]PEP 7,van Rossum写的C编码风格指南
[2]Barry的GNU Mailman风格指南
 http://barry.warsaw.us/software/STYLEGUIDE.txt
[3]http://www.wikipedia.com/wiki/CamelCase
[4]PEP 8现代化,2013年7月http://bugs.python.org/issue18472
版权
这个文档已经放置在公共领域。
来源:https://hg.python.org/peps/file/tip/pep-0008.txt



英文原文: https://www.python.org/dev/peps/pep-0008/

译者: wangyc


 

2月15日11:00到13:00网站停机维护,13:00前恢复
iPy智能助手 双击展开
查看更多聊天记录
(Ctrl+回车)换行