Python重构代码的一些模式

以下介绍一些python idiom,每当你在代码库中看到以下的模式可以参照以下的建议进行重构,让代码变得更加的pythonic,可读性更好,更容易维护。

代码示例Python版本为2.7

enumerate

需要使用列表的下标时,不要使用C风格的下标遍历

1
lst = ['a', 'b', 'c']
1
2
3
4
5
6
7
8
9
# DON'T
i = 0
for i in lst:
print i, '-->', lst[i]
i += 1

# OR
for i in range(len(lst)):
print i, '-->', lst[i]
1
2
3
# DO
for idx, item in enumerate(lst):
print idx, '-->', item

zip/izip

同时遍历两个列表时,不要使用C风格的下标遍历

1
2
lst1 = ['a', 'b', 'c']
lst2 = [1, 2, 3]
1
2
3
4
# DON'T
for i in range(len(lst1)):
print lst1[i]
print lst2[i]
1
2
3
4
# DO
for lst1_item, lst2_item in zip(lst1, lst2):
print lst1_item
print lst2_item
1
2
3
4
5
6
# BETTER
# 不需要在内存中生成包含lst, lst2的第三个列表
from itertools import izip
for lst1_item, lst2_item in izip(lst1, lst2):
print lst1_item
print lst2_item

unpacking tuple

swap

1
2
x = 10
y = -10
1
2
3
4
# DON'T
tmp = x
x = y
y = tmp
1
2
# DO
x, y = y, x

get element from tuple

1
words = ['apple', 'banana', 'cat']
1
2
3
# DON'T
foo = words[0]
bar = words[1]
1
2
# DO
foo, bar, _ = words # 使用 _ 如果你不需要这个值

Dict.setdefault/defaultdict

处理字典中key不存在时的默认值

1
2
3
# group words by frequency
words = [(1, 'apple'), (2, 'banana'), (1, 'cat')]
frequency = {}
1
2
3
4
5
# DON'T
for freq, word in words:
if freq not in frequency:
frequency[freq] = []
frequency[freq].append(word)
1
2
3
# DO
for freq, word in words:
frequency.setdefault(freq, []).append(word)

如果你知道自己在做什么,use defaultdict

1
2
3
4
5
# BETTER
from collections import defaultdict
frequency = defaultdict(list)
for freq, word in words:
frequency[freq].append(word)

Dict.iteritems

遍历字典

1
words = {'apple': 1, 'banana': 2, 'cat': 3}
1
2
3
# OK
for word in words:
print word, '-->', words[word] # 需要计算word的hash值
1
2
3
# GOOD
for word, freq in words.items():
print word, '-->', freq
1
2
3
4
# BETTER
# 不需要在内存中生存包含words所有元素的中间结果
for word, freq in words.iteritems():
print word, '-->', freq

for...else

break and nobreak

使用for...else模式处理遍历列表时搜索符合某些条件的元素break以及搜索不到符合条件元素时的情况。

有些人会认为for...else的语义让人迷惑所以不建议使用,但是正确地使用for...else模式会让代码的结构更加清晰。

1
2
3
# search if a word match some condition exists
words = ['apple', 'banana', 'cat']
condition = lambda word: len(word) == 1
1
2
3
4
5
6
7
8
9
# DON'T
found = False
for word in words:
if condition(word):
print 'Found'
found = True
break
if not found:
print 'Not found'
1
2
3
4
5
6
7
8
9
# DO
for word in words:
if condition(word):
# 处理存在符合condition的元素的情况
print 'Found'
break
else:
# 处理没有符合condition元素的情况
print 'Not found'

elsefor...else语法里的语义是如果没有break发生,也即是列表被完全遍历的情况。代码清晰地分成了两部分,你可以明确地看到他们属于同一个结构。

for...else语法让人迷惑的地方就是在于else这个名字,根据python核心开发者Raymond Hettinger的说法,如果他们有机会回到过去修改这一语法的话,他们会将else修改为nobreak

事实上如果不需要处理搜索到符合condition元素时的情况,只需要检查符合condition元素是否存在时,使用any

1
2
3
existed = any(map(condition, words))
if not existed:
print 'Not found'

try...except...else

分开异常处理与正常情况

try...except语法大家都不陌生,大多数情况下简单地使用地try...except...else语法将臃肿的try block重构就可以让代码的结构更加清晰,将异常处理和正常情况清晰地区分开来。

1
2
3
4
5
6
7
8
9
10
11
import json

def get_external_json():
return "maybe valid json, maybe some plain text of error"

def do_something_with(result):
print result

def handle_error(e):
# maybe log exception trace
print 'Oops'
1
2
3
4
5
6
# GOOD
try:
result = json.loads(get_external_json())
do_something_with(result)
except Exception as e:
handle_error(e)
1
2
3
4
5
6
7
8
9
10
# BETTER
try:
# 异常可能抛出点
result = json.loads(get_external_json())
except Exception as e:
# 异常处理
handle_error(e)
else:
# 正常情况
do_something_with(result)

try...except...else这个结构清晰地区分了异常可能的抛出点,异常处理及正常情况三种情况。