使你的Python代码更优雅

使你的Python代码更优雅

注: 本人代码主要以Python3为主。

遍历一个范围的数字

  1. Python2的普通写法

    1
    2
    for i in [0, 1, 2, 3, 4, 5]:
    print i ** 2

    或者:

    1
    2
    for i in range(6):
    print i ** 2
  2. Python2最好的优雅的写法是

    1
    2
    for i in xrange(6):
    print i ** 2
  3. 在Python3中的优雅写法

    1
    2
    for i in range(6):
    print(i ** 2)

xrange会返回一个迭代器,用来一次一个值地遍历一个范围。这种方式会比range更省内存。xrange在Python3中已经改名为range。

遍历一个集合

  1. 普通的写法

    1
    2
    3
    colors = ['r', 'g', 'b']
    for i in range(len(colors)):
    print(colors[i])
  2. 优雅的写法

    1
    2
    for color in colors:
    print(color)

反向遍历

  1. 普通的写法

    1
    2
    3
    colors = ['r', 'g', 'b']
    for i in range(len(colors) - 1, -1, -1):
    print(colors[i])
  2. 优雅的写法

    1
    2
    for color in reversed(colors):
    print(color)

遍历一个集合及其下标

  1. 普通的写法

    1
    2
    3
    colors = ['r', 'g', 'b']
    for i in range(len(colors)):
    print(i, '--->', colors[i])
  2. 优雅的写法

    1
    2
    for i, color in enumerate(colors):
    print(i, '--->', color)

说明: 这种写法效率高,优雅,而且可以省去亲自创建和自增下标。当你发现你在操作集合的下标时,你很有可能在做错事。

遍历两个集合

  1. Python2普通的写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    names = ['x', 'y', 'z']
    colors = ['r', 'g', 'b']

    n = min(len(names), len(colors))
    for i in range(n):
    print names[i], '--->', colors[i]

    for name, color in zip(names, colors):
    print name, '--->', color
  2. Python2优雅的写法

    1
    2
    for name, color in izip(names, colors):
    print(name, '--->', color)
  3. Python3中优雅的写法

    1
    2
    for name, color in zip(names, colors):
    print(name, '--->', color)

说明: zip在内存中生成一个新的列表,需要更多的内存。izip比zip效率更高。但是在Python3中,izip改名为zip,并替换了原来的zip成为内置函数。

有序地遍历

  1. 优雅的写法

    1
    print(sorted(colors, key=len))

调用一个函数直到遇到标记值

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    blocks = []
    while True:
    block = f.read(32)
    if block == '':
    break
    block.append(block)
  2. 优雅的写法

    1
    2
    3
    4
    import functools
    blocks = []
    for block in iter(functools.partial(f.read, 32), ''):
    blocks.append(block)

说明: 这个例子里不太能看出来方法二的优势,甚至觉得partial让代码可读性更差了。方法二的优势在于iter的返回值是个迭代器,迭代器能用在各种地方,se、sorte、mi、max、heapq、sum……

在循环内识别多个退出点

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def find(seq, target):
    flag = False
    for i, value in enumerate(seq):
    if value == target:
    flag = True
    break
    if not flag:
    return -1
    return i
  2. 优雅的写法

    1
    2
    3
    4
    5
    6
    7
    def find(seq, target):
    for i, value in enumerate(seq):
    if value == target:
    break
    else:
    return -1
    return i

    说明:此函数将在for执行完所有的循环后就会执行else。本人也是刚刚了解for-else语法,在什么情况下会执行到else里。这里有两种方法去理解else。

    • 把for看作if,当for后面的条件为False时执行else。其实条件为False时,就是for循环没被break出去,把所有循环都跑完的时候。

    • 把else记成nobreak,当for没有被break,那么循环结束时会进入到else。

遍历字典的key

1
2
3
4
5
6
7
d = {'x': 'red', 'y': 'g', 'z':'b'}
for k in d:
print(k)

for k in list(d.keys()):
if k.startwith(y):
del d[k]

说明: 什么时候应该使用第二种而不是第一种方法?当你需要修改字典的时候。

如果你在迭代一个东西的时候修改它,那就是在冒天下之大不韪,接下来发生什么都活该。

list(d.keys())把字典里所有的key都复制到一个列表里。然后你就可以修改字典了。

遍历一个字典的key和value

  1. Python2普通的写法

    1
    2
    3
    4
    5
    6
    7
    # 并不快,每次必须要重新哈希并做一次查找
    for k in d:
    print k, '--->', d[k]

    # 产生一个很大的列表
    for k, v in d.items():
    print k, '--->', v
  2. Python2优雅的写法

    1
    2
    for k, v in d.iteritems():
    print k, '--->', v
  3. Python3优雅的写法

    1
    2
    for k, v in d.items():
    print(k, '--->', v)

说明: Python3已经没有iteritems()了,items()的行为和iteritems()很接近。详情请看文档。

用key-value对构建字典

1
2
3
4
names = ['x', 'y', 'z']
colors = ['r', 'g', 'b']

d = dict(zip(names, colors))

用字典计数

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    7
    colors = ['r', 'g', 'r', 'b', 'g', 'b']

    d = {}
    for color in colors:
    if color not in d:
    d[color] == 0
    d[color] += 1
  2. 优雅的写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    colors = ['r', 'g', 'r', 'b', 'g', 'b']
    for color in colors:
    d[color] = d.get(color, 0) + 1

    # 稍微潮点的方法,但有些坑需要注意,适合熟练的老手。
    from collections import defaultdict
    d = defaultdict(int)
    for color in colors:
    d[color] += 1

用字典分组

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    names = ['raymond', 'rachel', 'matthew', 'roger',
    'betty', 'melissa', 'judith', 'charlie']
    d = {}
    for name in names:
    key = len(name)
    if key not in d:
    d[key] = []
    d[key].append(name)

    d = {}
    for name in names:
    key = len(name)
    d.setdefault(key, []).append(name)
  2. 优雅的写法

    1
    2
    3
    4
    d = defaultdict(list)
    for name in names:
    key = len(name)
    d[key].append(name)

连接字典

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    defaults =  {'color': 'r', 'user': 'x'}
    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--user')
    parser.add_argument('-c', '--color')
    namespace = parser.parse_args([])
    command_line_args = {k:v for k, v in vars(namespace).items() if v}

    d = defaults.copy()
    d.update(os.environ)
    d.update(command_line_args)
  2. 优雅的写法

    1
    2
    from collections import ChainMap
    d = ChainMap(command_line_args, os.environ, defaults)

提高可读性

  • 位置参数和下标很漂亮

  • 但关键字和名称更好

  • 第一种方法对于计算机来说很便利

  • 第二种方法和人类思考方式一致

  1. 普通的写法

    1
    search('@obama', False, 20, True)
  2. 优雅的写法

    1
    search('@obama', retweets=False, numtweets=20, popular=True)

第二种方法稍微(微秒级)慢一点,但为了代码的可读性和开发时间,值得。

使用namedtuple提高多个返回值的可读性

  1. 普通的写法

    1
    doctest.testmod()
  2. 优雅的写法

    1
    2
    from collections import namedtuple
    TestResults = namedTuple('TestResults', ['failed', 'attempted'])

unpack序列

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    7
    8
    p = 'Raymond', 'Hettinger', 0x30, 'python@example.com'

    # 其它语言的常用方法/习惯

    fname = p[0]
    lname = p[1]
    age = p[2]
    email = p[3]
  2. 优雅的写法

    1
    fname, lname, age, email = p

更新多个变量的状态

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    7
    8
    def fibnacci(n):
    x = 0
    y = 0
    for i in range(n):
    print(x)
    t = y
    y = x + y
    x = t
  2. 优雅的写法

    1
    2
    3
    4
    5
    def fibnacci(n):
    x, y = 0, 1
    for i in range(n):
    print(x)
    x, y = y, x + y

第一种方法的问题

  • x和y是状态,状态应该在一次操作中更新,分几行的话状态会互相对不上,这经常是bug的源头。

  • 操作有顺序要求

  • 太底层太细节

第二种方法抽象层级更高,没有操作顺序出错的风险而且更效率更高。

连接字符串

  1. 普通的写法

    1
    2
    3
    4
    5
    names = ['x', 'y', 'z']
    s = names[0]
    for name in names[1:]:
    s += ',' + name
    print(s)
  2. 优雅的写法

    1
    print(','.join(names))

更新序列

  1. 普通的写法

    1
    2
    3
    names = ['x', 'y', 'z']

    del names[0]
  2. 优雅的写法

    1
    2
    3
    4
    5
    6
    7
    from collections import deque
    names = deque(['x', 'y', 'z'])

    # 用deque更有效率
    del names[0]
    names.popleft()
    names.appendleft('mark')

装饰器和上下文管理

  • 用于把业务和管理的逻辑分开

  • 分解代码和提高代码重用性的干净优雅的好工具

  • 起个好名字很关键

  • 记住蜘蛛侠的格言:能力越大,责任越大

使用装饰器分离出管理逻辑

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    7
    def web_lookup(url, saved={}):
    if url in saved:
    return saved[url]

    page = urllib.urlopen(url).read()
    saved[url] = page
    return page
  2. 优雅的写法

    1
    2
    3
    @cache
    def web_lookup(url):
    return urllib.urlopen(url).read()

分离临时上下文

  1. 普通的写法

    1
    2
    3
    4
    5
    # 保存旧的,创建新的
    old_context = getcontext().copy()
    getcontext().prec = 50
    print(Decimal(355) / Decimal(113))
    setcontext(old_context)
  2. 优雅的写法

    1
    2
    with localcontext(Context(prec=50)):
    print(Decimal(355) / Decimal(113))

如何打开关闭文件

  1. 普通的写法

    1
    2
    3
    4
    5
    f = open('data.txt')
    try:
    data = f.read()
    finally:
    f.close()
  2. 优雅的写法

    1
    2
    with open('data.txt') as f:
    data = f.read()

如何使用锁

  1. 普通的写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 创建锁
    lock = threading.Lock()

    # 使用锁的老方法
    lock.acquire()
    try:
    print('Section 1')
    print('Section 2')
    finally:
    lock.releae()
  2. 优雅的写法

    1
    2
    3
    4
    lock = threading.Lock()
    with lock:
    print('Section 1')
    print('Section 2')

分离出临时的上下文

  1. 普通的写法

    1
    2
    3
    4
    try:
    os.remove('somefile.tmp')
    except OSError:
    pass
  2. 优雅的写法

    1
    2
    with ignore(OSError):
    os.remove('somefile.tmp')

简洁的单句表达

两个冲突的原则

  • 一行不要有太多逻辑

  • 不要把单一的想法拆分成多个部分

Raymond的原则

  • 一行代码的逻辑等于一句自然语句

列表解析和生成器

  1. 普通的写法

    1
    2
    3
    4
    5
    result = []
    for i in range(10):
    s = i ** 2
    result.append(s)
    print(sum(result))
  2. 优雅的写法

    1
    print(sum(i ** 2 for i in range(n)))