Python最佳实践指南 阅读笔记

创建将0到19连接起来的字符串

1
2
3
4
5
6
7
8
nums = []
for n in range(20):
nums.append(str(n))
print "".join(nums)
# 更好的写法
nums = [str(n) for n in range(20)]
print "".join(nums)

拼接多个已有的字符串

1
2
3
4
5
6
7
foo = 'foo'
bar = 'bar'
foobar = foo + bar # 好的做法
foo += 'ooo' # 不好的做法, 应该这么做:
foo = ''.join([foo, 'ooo'])

也可以使用 % 格式运算符来连接确定数量的字符串,但 PEP 3101 建议使用 str.format() 替代 % 操作符。

1
2
3
4
5
6
foo = 'foo'
bar = 'bar'
foobar = '%s%s' % (foo, bar) # 可行
foobar = '{0}{1}'.format(foo, bar) # 更好
foobar = '{foo}{bar}'.format(foo=foo, bar=bar) # 最好

不要重复使用命名

1
2
3
items = 'a b c d' # 首先指向字符串...
items = items.split(' ') # ...变为列表
items = set(items) # ...再变为集合

重复使用命名对效率并没有提升:赋值时无论如何都要创建新的对象。然而随着复杂度的 提升,赋值语句被其他代码包括 ‘if’ 分支和循环分开,使得更难查明指定变量的类型。 在某些代码的做法中,例如函数编程,推荐的是从不重复对同一个变量命名赋值。Java 内的实现方式是使用 ‘final’ 关键字。Python并没有 ‘final’ 关键字而且这与它的哲学 相悖。尽管如此,避免给同一个变量命名重复赋值仍是是个好的做法,并且有助于掌握 可变与不可变类型的概念。

考虑该不该用任意参数列表(*args)

如果一个函数接受的参数列表具有 相同的性质,通常把它定义成一个参数,这个参数是一个列表或者其他任何序列会更清晰。

函数单个出口可能更好

当一个函数在其正常过程中有多个主要出口点时,它会变得难以调试和返回其 结果,所以保持单个出口点可能会更好。这也将有助于提取某些代码路径,而且多个出口点 很有可能意味着这里需要重构。

1
2
3
4
5
6
7
8
9
10
11
def complex_function(a, b, c):
if not a:
return None # 抛出一个异常可能会更好
if not b:
return None # 抛出一个异常可能会更好
# 一些复杂的代码试着用a,b,c来计算x
# 如果成功了,抵制住返回x的诱惑
if not x:
# 一些关于x的计算的Plan-B
return x

常见Python习语

  • 解包
1
2
3
4
5
6
for index, item in enumerate(some_list):
# 使用index和item做一些工作
a, b = b, a
a, (b, c) = 1, (2, 3)
  • 创建一个被忽略的变量
1
2
filename = 'foobar.txt'
basename, __, ext = filename.rpartition('.')
  • 创建一个含N个对象的列表
1
four_nones = [None] * 4
  • 创建一个含N个列表的列表
1
four_lists = [[] for __ in xrange(4)]
  • 根据列表来创建字符串
1
2
letters = ['s', 'p', 'a', 'm']
word = ''.join(letters)
  • 在集合体(collection)中查找一个项
1
2
3
4
5
6
7
8
s = set(['s', 'p', 'a', 'm'])
l = ['s', 'p', 'a', 'm']
def lookup_set(s):
return 's' in s
def lookup_list(l):
return 's' in l

在下列场合在使用集合或者字典而不是列表,通常会是个好主意:

集合体中包含大量的项
你将在集合体中重复地查找项
你没有重复的项

你不需要明确地比较一个值是True,或者None,或者0

糟糕

1
2
3
4
5
if attr == True:
print 'True!'
if attr == None:
print 'attr is None!'

优雅

1
2
3
4
5
6
7
8
9
10
11
# 检查值
if attr:
print 'attr is truthy!'
# 或者做相反的检查
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!'

访问字典元素

糟糕

1
2
3
4
5
d = {'hello': 'world'}
if d.has_key('hello'):
print d['hello'] # 打印 'world'
else:
print 'default_value'

优雅

1
2
3
4
5
6
7
8
d = {'hello': 'world'}
print d.get('hello', 'default_value') # 打印 'world'
print d.get('thingy', 'default_value') # 打印 'default_value'
# Or:
if 'hello' in d:
print d['hello']

在每次函数调用中,通过使用指示没有提供参数的默认参数 None 通常是 个好选择),来创建一个新的对象。

举例:

1
2
3
def append_to(element, to=[]):
to.append(element)
return to

你可能认为

1
2
3
4
5
my_list = append_to(12)
print my_list # [12]
my_other_list = append_to(42)
print my_other_list # [42]

实际结果为

1
2
3
# [12]
# [12, 42]

当函数被定义时,一个新的列表就被创建一次 ,而且同一个列表在每次成功的调用中都被使用。

当函数被定义时,Python的默认参数就被创建 一次,而不是每次调用函数的时候创建。 这意味着,如果你使用一个可变默认参数并改变了它,你 将会 在未来所有对此函数的 调用中改变这个对象。

迟绑定闭包

举例

1
2
3
4
5
def create_multipliers():
return [lambda x : i * x for i in range(5)]
for multiplier in create_multipliers():
print multiplier(2)

你期望的结果

1
2
3
4
5
0
2
4
6
8

实际结果

1
2
3
4
5
8
8
8
8
8

五个函数被创建了,它们全都用4乘以 x 。

Python的闭包是 迟绑定 。 这意味着闭包中用到的变量的值,是在内部函数被调用时查询得到的。

这里,不论 任何 返回的函数是如何被调用的, i 的值是调用时在周围作用域中查询到的。 接着,循环完成, i 的值最终变成了4。

这个陷阱并不和 lambda 有关,不通定义也会这样

1
2
3
4
5
6
7
8
9
def create_multipliers():
multipliers = []
for i in range(5):
def multiplier(x):
return i * x
multipliers.append(multiplier)
return multipliers

解决方案

最一般的解决方案可以说是有点取巧(hack)。由于 Python 拥有为函数默认参数 赋值的行为,你可以创建一个立即绑定参数的闭包,像下面这样:

1
2
def create_multipliers():
return [lambda x, i=i : i * x for i in range(5)]

或者,可以使用 function.partial 函数

1
2
3
4
5
from functools import partial
from operator import mul
def create_multipliers():
return [partial(mul, i) for i in range(5)]