Python编码风格,看这篇就够了

如果有人问起 Python 程序员他们最喜欢 Python 哪一点,他们一定会提到 Python 的高可读性。确实,对于 Python 来说,其高可读性一直是 Python 这门语言设计的核心。一个不争的事实是,相对于写代码而言,读代码才是更加平常的事情。

Python 代码有高可读性的一个原因就是其有着相对而言更加完善的编码风格准则和 「Python 化」习语。

当 Python 老手(Pythonista)认为一段代码不「Python 化」,他们通常的意思是这段代码没有遵循一般准则,同时亦没有以最佳的(最具可读性的)方式表达出代码的意图。

在一些极端的情况下,没有公认最佳的方式来表达 Python 代码的意图,不过这种极端情况非常罕见。

一般概念

明确代码意义

尽管 Python 可以写出从各种意义上来说都像是黑魔法的代码,但最简单直白的表达才是正道。

不好

def make_complex(*args):
    x, y = args    return dict(**locals())


def make_complex(x, y):
    return {'x': x, 'y': y}

在上述好的代码中,x 和 y 清晰明了的从参数中获取值,并清晰明了的返回了一个字典。当开发者看到这个函数后就可以明了这个函数的用途,而不好的代码则不行。

一行一个声明语句

虽然在 Python 中我们推崇使用形如列表生成式这种简洁明了的复合语句,但是除此以外,我们应该尽量避免将两句独立分割的代码写在同一行。

不好的风格

print 'one'; print 'two'

if x == 1: print 'one'

if <complex comparison> and <other complex comparison>:
    # do something

好的风格

print 'one'
print 'two'

if x == 1:
    print 'one'

cond1 = <complex comparison>
cond2 = <other complex comparison>
if cond1 and cond2:
    # do something

函数的参数

函数的参数可以使用四种不同的方式传递给函数。

必选参数 是没有默认值的必填的参数。 必选参数是最简单的参数构成,用于参数较少的函数的构成,是该函数意义的一部分,使用他们的顺序是按照定义自然排序的。举个例子,对于 send(message, recipient) 和  point(x, y) 这两个函数,使用函数的人需要知道这个函数需要两个参数,并且记住两个参数的顺序。

在调用函数的时候,我们也可以使用参数的名称调用。使用参数的名称的方式可以调换参数的顺序,就像 send(recipient='World',message='Hello') 和 point(y=2, x=1) 这样。但这样的做法会降低代码的可读性,并且使代码冗长,因此更建议使用 send('Hello', 'World') 和 point(1,2) 这样的方式调用。

关键字参数 是非强制的,且有默认值。它们经常被用在传递给函数的可选参数中。 当一个函数有超过两个或三个位置参数时,函数签名会变得难以记忆,使用带有默认参数的关键字参数有时候会给你带来便利。比如,一个更完整的 send 函数可以被定义为 send(message, to, cc=None, bcc=None)。这里的 cc 和 bcc 是可选的, 当没有传递给它们其他值的时候,它们的值就是 None。

Python 中有多种方式调用带关键字参数的函数。比如说,我们可以按照定义时的参数顺序而无需明确的命名参数来调用函数,就像 send('Hello', 'World', 'Cthulhu', 'God') 是将密件发送给上帝。我们也可以使用命名参数而无需遵循参数顺序来调用函数,就像 send('Hello again', 'World', bcc='God', cc='Cthulhu') 。没有特殊情况的话,这两种方式都需要尽力避免,最优的调用方式是与定义方式一致:send('Hello', 'World', cc='Cthulhu',bcc='God') 。

任意参数列表 是第三种给函数传参的方式。如果函数的参数数量是动态的,该函数可以被定义成 *args 的结构。在这个函数体中, args 是一个元组,它包含所有剩余的位置参数。举个例子, 我们可以用任何容器作为参数去调用 send(message, *args) ,比如 send('Hello', 'God', 'Mom','Cthulhu')。 在此函数体中, args 相当于 ('God','Mom', 'Cthulhu')。

然而,这种结构有一些缺点,使用时应该特别注意。如果一个函数接受的参数列表具有相同的性质,通常把它定义成一个参数,这个参数是一个列表或者其他任何序列会更清晰。 在这里,如果 send 参数有多个容器(recipients),将之定义成 send(message,recipients) 会更明确,调用它时就使用 send('Hello', ['God', 'Mom', 'Cthulhu'])。这样的话, 函数的使用者可以事先将容器列表维护成列表(list)形式,这为传递各种不能被转变成其他序列的序列(包括迭代器)带来了可能。

任意关键字参数字典 是最后一种给函数传参的方式。如果函数要求一系列待定的命名参数,我们可以使用 **kwargs 的结构。在函数体中, kwargs 是一个字典,它包含所有传递给函数但没有被其他关键字参数捕捉的命名参数。

和 任意参数列表 中所需注意的一样,相似的原因是:这些强大的技术在非特殊情况下,都要尽量避免使用,因为其缺乏简单和明确的结构来足够表达函数意图。

编写函数的时候采用何种参数形式,是用位置参数,还是可选关键字参数,是否使用形如任意参数 的高级技术,这些都由程序员自己决定。如果能明智地遵循上述建议,即可轻松写出这样的 Python 函数:

易读(名字和参数无需解释)

易改(添加新的关键字参数不会破坏代码的其他部分)

避免魔法方法

Python 对骇客来说是一个强有力的工具,它拥有非常丰富的钩子(hook)和工具,允许你施展几乎任何形式的技巧。比如说,它能够做以下:

改变对象创建和实例化的方式;

改变 Python 解释器导入模块的方式;

甚至可能(如果需要的话也是被推荐的)在 Python 中嵌入 C 程序。

尽管如此,所有的这些选择都有许多缺点。使用最直接的方式来达成目标通常是最好的方法。它们最主要的缺点是可读性不高。许多代码分析工具,比如说 pylint 或者 pyflakes,将无法解析这种『魔法』代码。

我们认为 Python 开发者应该知道这些近乎无限的可能性,因为它为我们灌输了没有不可能完成的任务的信心。然而,知道何时 不能 使用它们也是非常重要的。