Python代码风格

声明:摘自Code Style

如果你问一个Python程序员他最喜欢Python里的什么,他通常会说高可阅读性。事实上,便于阅读是Python语言设计的核心,这是基于这样一个事实,阅读代码通常比写代码的时候多。

Python代码利于阅读的一个原因是它有一个相对完善的代码风格指南和“Pythonic“原语。

当一个Python老鸟指着一块代码说不够“Pythonic”的时候,通常意味着这段代码没有遵守公共的规范,并且没有用最好的方式表达内在的意图。

在一些例子里,怎样用Python代码来表达一个意图,并没有一致认同的最好的方式。但是,这种例子是很少见的。

精确的代码

当有好多黑魔法可用的时候,最精确直接的方式是首选。

Bad

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

Good

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

好代码中,接受了x,y并返回一个字典。使用这个函数的开发者通过首尾两行代码很容易就知道怎样使用。

一行一条语句

一些复合语句如list comprehensions可打破此例,但把两条不相关的语句写在一行里则是差的实践。

Bad

print 'one'; print 'two'

if x == 1: print 'one'

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

Good

print 'one'
print 'two'

if x == 1:
    print 'one'

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

函数参数

向一个函数传递参数有四种方法。

  1. Positional arguments是必选参数并且没有默认值。这是最简单的一种参数形式,可以用在参数是函数的全部意思并且参数顺序是自然的。例如在send(message, recipient)或者point(x, y)中,用户很容易记住这两个参数和顺序。

    在这两个例子里,使用参数名调用也是可以的,甚至还可以改变顺序,如send(recipient='World', message='Hello')或point(y=2, x=1),但是和前一种方法相比,降低了可读性而且变得冗长。

  2. Keyword arguments是可选参数并且有默认值。当一个函数有两或三个positional 参数时,函数会变得很难记忆,用keyword参数会有所改善。例如一个更复杂的send函数,send(message, to, cc=None, bcc=None)。这里cc和hcc是可选的,并且当没有传入参数是会赋值为None。

    可以通过多种方式调用一个有keyword参数的函数。例如,不使用参数名send('Hello', 'World', 'Cthulhu', 'God'),改变参数顺序send('Hello again', 'World', bcc='God', cc='Cthulhu'),但是没有特别原因最好还是使用这种接近函数定义的方式send('Hello', 'World', cc='Cthulhu', bcc='God')。

    一些可选参数为了以防万一但是看起来好像永远不会用到,按照YAGNI原则,删除可选参数要比添加一个可选参数更困难。

  3. arbitrary argument list是第三种参数形式。当一个函数的意图可以通过任意个positional参数更好表达时,可以使用args构造。在函数体内,args会是包含所有positional参数的元组。例如send(message, args)可以这样调用send('Hello', 'God', 'Mom', 'Cthulhu'),在函数体内args等同于('God', 'Mom', 'Cthulhu')。

    在这里定义成这样更好send(message, recipients),调用的时候传入一个列表send('Hello', ['God', 'Mom', 'Cthulhu'])。

  4. arbitrary keyword argument dictionary是第四种方法。

返回值

def complex_function(a, b, c):
    if not a:
        return None  # Raising an exception might be better
    if not b:
        return None  # Raising an exception might be better
    # Some complex code trying to compute x from a, b and c
    # Resist temptation to return x if succeeded
    if not x:
        # Some Plan-B computation of x
    return x  # One single exit point for the returned value x will help
              # when maintaining the code.

Idioms

  1. Unpacking

    for index, item in enumerate(some_list):
        # do something with index and item
    
    a, b = b, a
    
    a, (b, c) = 1, (2, 3)
    
  2. Create an ignored variable

    filename = 'foobar.txt'
    basename, __, ext = filename.rpartition('.')
    
  3. Create a length-N list of the same thing

    four_nones = [None] * 4
    
  4. Create a length-N list of lists

    four_lists = [[] for __ in xrange(4)]
    
    letters = ['s', 'p', 'a', 'm']
    word = ''.join(letters)
    
    d = {'s': [], 'p': [], 'a': [], 'm': []}
    l = ['s', 'p', 'a', 'm']
    
    def lookup_dict(d):
        return 's' in d
    
    def lookup_list(l):
        return 's' in l
    

PEP8

检测代码是否符合PEP8规范

pip install pep8

约定

  1. 检测变量

    Bad:

    if attr == True:
        print 'True!'
    
    if attr == None:
        print 'attr is None!'
    

    Good:

    # Just check the value
    if attr:
        print 'attr is truthy!'
    
    # or check for the opposite
    if not attr:
        print 'attr is falsey!'
    
    # or, since None is considered false, explicitly check for it
    if attr is None:
        print 'attr is None!'
    
  2. 访问Dict元素

    Bad:

    d = {'hello': 'world'}
    if d.has_key('hello'):
        print d['hello']    # prints 'world'
    else:
        print 'default_value'
    

    Good:

    d = {'hello': 'world'}
    
    print d.get('hello', 'default_value') # prints 'world'
    print d.get('thingy', 'default_value') # prints 'default_value'
    
    # Or:
    if 'hello' in d:
        print d['hello']
    
  3. 操作List

    Bad:

    # Filter elements greater than 4
    a = [3, 4, 5]
    b = []
    for i in a:
        if i > 4:
            b.append(i)
    

    Good:

    b = [i for i in a if i > 4]
    b = filter(lambda x: x > 4, a)
    

    Bad:

    # Add three to all list members.
    a = [3, 4, 5]
    count = 0
    for i in a:
        a[count] = i + 3
        count = count + 1
    

    Good:

    a = [3, 4, 5]
    a = [i + 3 for i in a]
    # Or:
    a = map(lambda i: i + 3, a)
    for i, item in enumerate(a):
        print i + ", " + item
    # prints
    # 0, 3
    # 1, 4
    # 2, 5
    
  4. 读取文件

    Bad:

    f = open('file.txt')
    a = f.read()
    print a
    f.close()
    

    Good:

    with open('file.txt') as f:
        for line in f:
            print line
    
  5. 换行

    Bad:

    my_very_big_string = """For a long time I used to go to bed early. Sometimes, \
        when I had put out my candle, my eyes would close so quickly that I had not even \
        time to say “I’m going to sleep.”"""
    
    from some.deep.module.inside.a.module import a_nice_function, another_nice_function, \
        yet_another_nice_function
    

    Good:

    my_very_big_string = (
        "For a long time I used to go to bed early. Sometimes, "
        "when I had put out my candle, my eyes would close so quickly "
        "that I had not even time to say “I’m going to sleep.”"
    )
    
    from some.deep.module.inside.a.module import (
        a_nice_function, another_nice_function, yet_another_nice_function)
    

其他资源

  1. http://c2.com/cgi/wiki?ProgrammingIdiom
  2. http://stackoverflow.com/questions/302459/what-is-a-programming-idiom
  3. http://stackoverflow.com/questions/513882/python-list-vs-dict-for-look-up-table
  4. http://stackoverflow.com/questions/228181/zen-of-python
  5. http://artifex.org/~hblanks/talks/2011/pep20_by_example.pdf
by Jungledrum