1.1 If 语句
1.1.1 避免直接和True,False,None进行比较
对于任何一个对象,无论是内建的还是用户自定义的,都有一个与其相关联的“真”。当检查一个条件是否为真时,在条件语句中优先使用对象隐含的“真”。视为真的规则是显而易见的。以下是视为False的情况:
- None
- False
- 数值类型的0
- 空序列
- 空字典
- 调用__len__或者__nonzero__返回0或者False时
其余所有情况皆为True(而且大多是隐式True)。通过__len__或者__nonzero__的返回值判断为False,允许你在类中定义“真”满足时的具体行为。
你应该在Python的if语句中使用隐式的“真”,而不是这样检查变量foo是否为True
if foo == True:
你应该简单的使用 if foo:.
有一些这样做的原因,最明显的是如果你的代码改变,foo变成int类型,而不再是True或者False。你的if语句仍能工作。更深层次的原因是基于equlity和identity的不同。使用==判断是否两个对象有相同的值,使用is判断两个对象是否是同一个对象。
因此,避免直接与False、None、空序列、空字典进行比较。如果一个名为my_list的列表为空,调用if my_list:计算结果是False.
有时需要和None直接进行比较,虽然我们不建议这样做,但有几种情况必须这样做。一个函数需要检查默认值为None的参数是否被设置,必须直接与None进行比较,如下所示:
def insert_value(value, position = None):
"""Inserts a value into my container, optionally at the
specified position"""
if position is not None:
...
if position: 会出什么错呢?如果有人想要向位置0处插入一个值,函数的行为就好像position没有被设置,因为0的计算结果为False.注意is和is not:的用法,当需要和None (python中的一个单例对象)进行比较时,应该使用is 或者 is not,而不是==.
现在利用python的“真”做一些工作。
1.1.1.1 糟糕的写法
def number_of_evil_robots_attacking():
return 10
def should_raise_shields():
# "We only raise Shields when one or more giant robots attack,
# so I can just return that value..."
return number_of_evil_robots_attacking()
if should_raise_shields() == True:
raise_shields()
print('Shield raised')
else:
print('Safe! No giant robots attacking')
1.1.1.2 地道的表达
def number_of_evil_robots_attacking():
return 10
def should_raise_shields():
# "We only raise Shields when one or more giant robots attack,
# so I can just return that value..."
return number_of_evil_robots_attacking()
if should_raise_shields():
raise_shields()
print('Shields raised')
else:
print('Safe! No giant robots attacking')
1.1.2 避免在复合if语句中重复变量名
当你想要检查一个变量和一系列的值进行比较,重复列出被检查变量是没有必要的冗长。使用一个可迭代的对象使代码变的更清晰,从而提高可读性。
1.1.2.1 糟糕的写法
is_generic_name = False
name = 'Tom'
if name == 'Tom' or name == 'Dick' or name == 'Harry':
is_generic_name = True
1.1.2.2 地道的表达
name = 'Tom'
is_generic_name = name in ('Tom', 'Dick', 'Harry')
1.1.3 避免在冒号后面放置条件分支的代码
使用标识符来表明作用域(就像你在python中已经做的那样)使确定什么作为条件分支的一部分将被执行变的容易,if,elif,else语句必须从新的一行开始。
1.1.3.1 糟糕的写法
name = 'Jeff'
address = 'New York, NY'
if name : print(name)
print(address)
1.1.3.2 地道的表达
name = 'Jeff'
address = 'New York, NY'
if name:
print(name)
else:
print(address)
1.2 For循环
1.2.1 在循环里使用enumerate函数而不是定义一个索引变量
其它语言的编程者习惯在循环中显式的声明一个变量用来追踪容器的索引。以C++为例:
for (int i = 0; i < container.size(); ++i)
{
// Do stuff
}
在python中内建的enumerate函数来扮演这个角色。
1.2.1.1 糟糕的写法
my_container = ['Larry', 'Moe', 'Curly']
index = 0
for element in my_container:
print('{} {}'.format(index, element))
index += 1
1.2.1.2 地道的表达
my_container = ['Larry', 'Moe', 'Curly']
for index, element in enumerate(my_container):
print('{} {}'.format(index, element))
1.2.2 使用in关键字来迭代可迭代的对象
来自其他语言的编程者缺乏使用for_each风格的结构即通过索引迭代容器得到每一个元素。Python中的in关键字优雅的处理这个问题。
1.2.2.1 糟糕的表达
my_list = ['Larry', 'Moe', 'Curly']
index = 0
while index < len(my_list):
print(my_list[index])
index += 1
1.2.2.2 地道的表达
my_list = ['Larry', 'Moe', 'Curly']
for element in my_list:
print(element)
1.2.3 For循环结束以后使用else来执行代码
很少有人知道的一个事实:python的for循环可以包含else语句。当迭代器迭代完成之后,else语句将会执行,除非因为break语句循环提前结束。这一特性允许你在for循环中检查一个条件,如果这个条件得到了某个元素,循环中断,如果循环结束,条件没有得到任何元素,else语句执行。这避免了在循环中单独使用条件标志来判断条件是否满足。
在下面的方案中我们运行报告来检查用户注册的邮箱是否是无效的。地道的版本由于不用处理has_malformed_email_address标志,因而更加简洁。而且,既是一个人并不熟悉for…slse表达,我们的代码也足够清晰教会他们。
1.2.3.1 糟糕的写法
for user in get_all_users():
has_malformed_email_address = False
print('Checking {}'.format(user))
for email_address in user.get_all_email_address():
if email_is_malformed(email_address):
has_malformed_email_address = True
print('Has a malformed email address!')
break
if not has_malformed_email_address:
print('All email address are valid!')
1.2.3.2 地道的表达
for user in get_all_users():
print('Checking {}'.format(user))
for email_address in user.get_all_email_address():
if emil_is_malformed(email_address):
print('Has a malformed email address!')
break
else:
print('All emil addresses are valid!')
1.3 函数
1.3.1 避免使用’ ’,[],{}作为函数的默认参数
尽管在Python教程里明确提及,然而,即使经验丰富的开发者依然感到吃惊。总之:优先使用names=None而不是names=[]作为函数的默认参数。下列是Python指南解决这个问题的方法。
1.3.1.1 糟糕的写法
# The default value [of a function] is evaluated only once.
# This makes a difference when the default is a mutable object
# such as a list, dictionary, or instances of most classes. For
# example, the following function accumulates the arguments
# passed to it on subsequent calls.
def f(a, L = []):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
# This will print
#
# []
# [1, 2]
# [1, 2, 3]
1.3.1.2 地道的表达
# If you don't want the default to be shared between subsequent # calls, you can write the function like this instead:
def f(a, L = None):
if L is None:
L = []
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
# This will print
# [1]
# [2]
# [3]
1.3.2 使用*args和**kwargs接收任意个参数
函数通常需要接收任意个数的位置参数或者关键字参数的列表,使用参数列表的子集合,转交剩下的参数给下一个函数。使用*args和**kwargs作为参数,允许函数分别接收任意个位置参数和keyword参数的列表。
在管理API向下兼容性时这种风格也有用。如果我们的函数可以接收任意个参数,我们可以在新的版本中任意添加新的参数。我们可以在不改变已存在的使用更少参数的代码的前提下,在新版本的函数中自由添加新的参数。只要所有的事情都被合适的记录,实际的参数就不重要了。
1.3.2.1 糟糕的写法
def make_api_call(foo, bar, baz):
if baz in ('Unicorn', 'Oven', 'New York'):
return foo(bar)
else:
return bar(foo)
# I need to add another parameter to `make_api_call`
# without breaking everyone's existing code.
# I have two options...
def so_many_options():
# I can tack on new parameters, but only if I make
# all of them opitonal...
def make_api_call(foo, bar, baz, qux = None, foo_polarity = None,
baz_coefficient = None, quux_capacitor = None,
bar_has_hopped = None, true = None, false = None,
file_not_found = None):
# ... and so on ad infinitum
return file_not_found
def version_graveyard():
# ... or I can create a new function each time the signature
# changes.
def make_api_call_v2(foo, bar, baz, qux):
return make_api_call(foo, bar, baz) - qux
def make_api_call_v3(foo, bar, baz, qux, foo_polarity):
if foo_polarity != 'reversed':
return make_api_call_v2(foo, bar, baz, qux)
return None
def make_api_call_v4(
foo, bar, baz, qux, foo_polarity, baz_coefficient):
return make_api_call_v3(
foo, bar, baz, qux, foo_polarity) * baz_coefficient
def make_api_call_v5(
foo, bar, baz, qux, foo_polarity,
baz_coefficient, quux_capacitor):
# I don't need 'foo', 'bar', or 'baz' anymore, but I have to
# keep supporting them...
return baz_coefficient * quux_capacitor
def make_api_call_v6(
foo, bar, baz, qux, foo_polarity, baz_coefficient,
quux_capacitor, bar_has_hopped):
if bar_has_hopped:
baz_coefficient *= -1
return make_api_call_v5(foo, bar, baz, qux,
foo_polarity, baz_coefficient,
quux_capacitor)
def make_api_call_v7(
foo, bar, baz, qux, foo_polarity, baz_coefficient,
quux_capacitor, bar_has_hopped, true):
return true
def make_api_call_v8(
foo, bar, baz, qux, foo_polarity, baz_coefficient,
quux_capacitor, bar_has_hopped, true, false):
return false
def make_api_call_v9(
foo, bar, baz, qux, foo_polarity, baz_coefficient,
quux_capacitor, bar_has_hopped,
true, false, file_not_found):
return file_not_found
1.3.2.2 地道的表达
def make_api_call(foo, bar, baz):
if baz in ('Unicorn', 'Oven', 'New York'):
return foo(bar)
else:
return bar(foo)
# I need to add another parameter to `make_api_call`
# without breaking everyone's existing code.
# Easy...
def new_hotness():
def make_api_call(foo, bar, baz, *args, **kwargs):
# Now I can accept any type and number of arguments
# without worrying about breaking existing code.
baz_coefficient = kwargs['the_baz']
# I can even forward my args to a different function without
# knowing their contents!
return baz_coefficient in new_function(args)