您的位置:首页 > 编程语言 > Python开发

如何编写优雅(地道)的Python代码 - 第一部分

2018-01-22 10:11 811 查看

1. 控制结构和函数

1.1 if语句

1.1.1 避免直接和True、False或者None进行比较

对于任意对象,内建还是用户定义的,本身都有真假的判断。当判断条件是否为真时,主要依赖于对象在条件语句中的真假性。真假性判断是非常清晰的。所有的下列条件都判断为假:
None
False
数值类型的0
空的序列
空的字典
当调用对象的len或nonzero方法时,返回值是0或False
其他的所有情况都判断为真(大部分情况是隐式为真)。最后一个通过检查len或nonzero方法返回值的判断条件,允许自定义的类如何决定对象的真假。
应该像Python中的if语句一样,在判断语句中隐式地利用真假。譬如下面判断变量foo是否为真的语句
if foo == True:

可以简单地写成
if foo:

写成下面的原因有很多。最明显的就是当代码发生变化时,譬如foo从True或False变成int时,if语句仍然可以工作。不过深层次而言,判断真假是基于对象间的equality和identity。使用==判断两个对象是否包含同样的值(具体由_eq属性定义)时。使用is判断两个对象是否同一对象(应用相同)。
注意:虽然有些情况下条件成立,就像比较对象的之间的相等性一样,这些是特殊情况,不要依赖。
因此,避免直接和False、Node以及像[], {}, ()这样的空序列比较真假。如果my_list列表类型为空,直接调用
if my_list:

结果就是假的。
有些时候,直接和None比较是必须的,虽然不是推荐的方式。函数中的某一个形参默认值为None时,而实际实参传递非空时,必须要将形参和None进行比较:
def insert_value(value, position=None):
"""Inserts a value into my container, optionally at thespecified position"""
if position is not None:
...

如果写成

if position:

问题出在哪里?如果想要在位置0插值,函数没有任何赋值操作,因为当position为0时,判断为假。注意和None比较时,应该总是使用is或is not,而不是==(参考PEP8).

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('Shields raised')
else:
print('Safe! No giant robots attacking')

1.1.1.2 python的风格

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 避免在复合条件语句中重复变量名称

当检测变量是否对应一些列值时,重复使用变量进行数值比较是不必要的。使用迭代的方式可以让代码更加清晰,并且可读性更好。

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 python的风格

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 python的风格

name = 'Jeff'
address = 'New York, NY'
if name:
print(name)
print(address)


1.2 for循环

1.2.1 在循环中使用enumerate函数而非创建index索引变量

其它编程语言的程序员经常显式声明一个索引变量,用于跟踪循环中的容器。譬如,在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 python的风格

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 python的风格

my_list = ['Larry', 'Moe', 'Curly']
for element in my_list:
print (element)

1.2.3 for循环结束后使用else执行代码

很少有人知道for循环语句后面可以跟随一段else语句。当迭代器执行结束后,else语句才会被执行,除非循环因为break语句提前结束了。break语句允许在循环内检查某些条件,当条件满足时,结束循环,否则,继续执行直到循环结束(无论如何最终else语句还是需要执行的)。这样可以避免通过在循环内增加标志位来检查条件是否满足。

+

在下面的情况下,代码用于检测用户注册的邮件地址是否合法(一个用户可以注册多个地址)。python风格的代码比较简洁,得益于不用处理has_malformed_email_address标志。更何况,即使一个程序员不熟悉for .. else语法,他们也可以很容易地看懂代码。

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_addresses():
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 addresses are valid!')

1.2.3.2

for user in get_all_users():
print ('Checking {}'.format(user))
for email_address in user.get_all_email_addresses():
if email_is_malformed(email_address):
print ('Has a malformed email address!')
break
else:
print ('All email addresses are valid!')

1.3 函数

1.3.1 避免使用'', []和{}作为函数参数的默认值

虽然在python教程中明确提到了这一点,很多人表示诧异,即使有经验的开发人员。简而言之,函数参数的默认值首选None,而非[]。下面是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]
# [1, 2]
# [1, 2, 3]

1.3.1.2 python的风格

# 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作为参数允许函数接受任意的位置和关键字参数列表。在维护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 optional...
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 python的风格

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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: