翻译《Writing Idiomatic Python》(一):if语句、for循环

开篇废话

这是在美国Amazon上评价很不错的一本书,其实严格来说这可能不算书,而是一本小册子。就像书名一样,里面的内容主要是用一些例子讲述地道的Python的代码是怎样写的。书中把很多例子用不良风格和地道Python写法作对比,内容覆盖谈不上很全,但是每一条都很有代表性。总体而言非常适合新手,同时里面有些条目老手看了或许也会有豁然开朗的感觉。作者Jeff Knupp曾在全球最牛B的高盛和其他银行里做过金融系统开发,在北美Python社区里也很有活跃度。

自己用Python也有些年头了,做过一年多的商业开发,不过其他大部分还是以科研和预研期的算法为主。最近因为又开始用Python做商业开发,所以想着顺便找些书看看,无意中看到了这本小书,觉得很不错,国内没有卖的,更别提中文版了。翻译这本书,算是复习和重新思考下Python,同时也会有少量自己的见解(C++风格注释绿色粗体),希望能坚持下去吧。我看的版本主要分为四部分:Control Structures and Functions(控制结构和函数)、Working with Data(数据和类型)、Organizing Your Code(代码组织)、General Advice(一般性建议)。每一部分里又分为不同的小章节,一共二十几个。我会按这个顺序不定期放出数目不定的章节。本人英文水平尚可,不过没有翻译经验,虽然不知道会不会有人关注这个系列,还是希望如果有看官,请轻拍指正:)

原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/

下一篇:翻译《Writing Idiomatic Python》(二):函数、异常


1. 控制结构和函数

1.1 if语句

1.1.1 通过链式比较让语句更加简明

当使用if语句时,优先使用链式比较操作,不仅会让语句更加简明,也会让执行效率更好。

不良风格:

1 if x <= y and y <= z:
2     return True

地道Python:

1 if x <= y <= z:
2     return True

// Python解释执行以上两种不同的比较方式时,其实都是先比较x<=y,如果为真,再比较y<=z。主要的区别在于,链式比较时,会先取y的值,然后复制压栈,整个过程中y的求值只执行了一次,而用and的方式时,y的求值会执行两次,也就是说,如果比较的是三个函数或者复杂的对象的话,链式比较只会求值三次,而通过and比较的方式则会求值4次。这大概就是为什么作者说执行效率会更好,但实际上如果只是简单的变量进行比较,效率未必会有提高。

1.1.2 避免将条件分支中的代码和冒号放在同一行

使用缩进来表示代码块的结构会让人更容易判断条件分支的代码结构。ifelifelse语句应该都总是独占一行,在冒号后没有代码。

不良风格:

1 name = 'Jeff'
2 address = 'New York, NY'
3 
4 if name: print(name)
5 print(address)

地道Python:

1 name = 'Jeff'
2 address = 'New York, NY'
3 
4 if name:
5     print(name)
6 print(address)

// 文件中的代码应该遵循这个规则,在控制台下放一行也未尝不可

1.1.3 避免在复合的if语句中重复出现同一个变量名

当想用if语句检查一个变量是否和许多值中的一个相等时,用==or重复写许多遍是否相等的检查会显得代码很冗长。简洁的写法是判断该变量是否在一个可遍历的结构中。

不良风格:

1 is_generic_name = False
2 name = 'Tom'
3 if name == 'Tom' or name == 'Dick' or name == 'Harry':
4     is_generic_name = True

地道Python:

1 name = 'Tom'
2 is_generic_name = name in ('Tom', 'Dick', 'Harry')

1.1.4 避免直接与True, False或者None直接比较

对于任意Python中的对象,无论是内建的还是用户定义的,本身都会关联一个内部的“真值”(truthiness)。所以很自然地,当判断一个条件是否为真的时候,尽量在条件判断语句中优先依靠这个隐式的“真值”。下面列举的是“真值”为False的情况:

None
False
数值0
空的序列(列表,元组等)
空的字典
当__len__或者__nonzero__被调用后返回的0值或者False

按照上面的最后一条,通过检查调用__len__或者__nonzero__后返回的值的方式,我们也可以定义自己创建的类型的“真值”。除了上面列举的这些,其他的情况都被认为“真值”为True

在Python中if语句隐式地使用“真值”,所以你的代码中也应该这样做。比如对于下面这种写法:

if foo == True:

更简单而直接的写法是:

if foo:

这样做的理由有很多。最明显的一条理由是,如果你的代码发生了变化,比如当foo变成了一个int型而不是TrueFalseif语句在判断是否为0时仍然正确。在更深的层面上,这是基于相等性(equality)和等价性(identity)的差别。使用==检查的是两个对象是否有相等或是等效的值(由_eq属性定义),而is语句则检查的是两个对象在底层是否同一个对象。

// Python对相等的实现在C代码中实现将比较对象用PyInt_AS_LONG转化成long型,然后再用C中的==进行比较,而is的实现是直接==比较。

所以对FalseNone和和空的序列比如[], {},以及()应该避免直接进行比较。如果一个叫my_list的列表为空, if my_list 会判断为False。当然有些情况下,虽然不推荐,但是直接和None比较是必须的。当在一个函数中需要判断一个默认值为None的参数是否被赋值的时候,比如:

1 def insert_value(value, position=None):
2     """向自定义的容器中插入一个值,插入值
3     的位置作为可选参数,默认值为None"""
4     if position is not None:
5         ...

如果使用 if position: 的话,哪里会出错呢?设想如果有人想在0位置插入一个值,那么函数会认为position这个参数没有设置,因为 if 0: 会判定为False。注意这里使用的是is not,根据PEP8,和None比较应该总是用is或者is not而不是==

总之,就让Python的“真值”代替你做比较的工作。

不良风格:

 1 def number_of_evil_robots_attacking():
 2     return 10
 3     
 4 def should_raise_shields():
 5     # 只有当一只以上的巨型机器人进攻时才打开防护罩
 6     # 所以我只需要返回巨型机器人的数量,如果不为零会自动判断为真
 7     return number_of_evil_robots_attacking()
 8 
 9 if should_raise_shields() == True:
10     raise_shields()
11     print('防护罩已打开')
12 else:
13     print('安全!并没有巨型机器人在进攻')

地道Python:

 1 def number_of_evil_robots_attacking():
 2     return 10
 3     
 4 def should_raise_shields():
 5     # 只有当一只以上的巨型机器人进攻时才打开防护罩
 6     # 所以我只需要返回巨型机器人的数量,如果不为零会自动判断为真
 7     return number_of_evil_robots_attacking()
 8 
 9 if should_raise_shields():
10     raise_shields()
11     print('防护罩已打开')
12 else:
13     print('安全!并没有巨型机器人在进攻')

1.1.5 使用if 和 else作为三元操作符的替代

和许多其他语言不同,Python没有三元操作符(比如: x ? true : false)。不过Python可以将赋值推迟到条件判断之后,所以在Python中三元操作可以用条件判断来替代。当然需要注意的是,除非是很简单的语句,否则三元操作的替代方案会让语句的可读性降低。

不良风格:

1 foo = True
2 value = 0
3 
4 if foo:
5     value = 1
6 
7 print(value)

地道Python:

1 foo = True
2 
3 value = 1 if foo else 0
4 
5 print(value)

1.2 For循环

1.2.1 在循环中使用enumerate函数来创建计数或索引

在许多其他语言中,开发者习惯显式地声明一个变量用来作为循环中的计数或者相关容器的索引。例如在C++中:

1 for ( int i = 0; i < container.size(); ++i )
2 {
3     // Do stuff
4 }

在Python中,内置的enumerate函数就可以很自然地处理这种需要。

不良风格:

1 my_container = ['Larry', 'Moe', 'Curly']
2 index = 0
3 for element in my_container:
4     print('{} {}'.format(index, element))
5     index += 1

地道Python:

1 my_container = ['Larry', 'Moe', 'Curly']
2 for index, element in enumerate(my_container):
3     print('{} {}'.format(index, element))

1.2.2 使用in关键字遍历可迭代结构

在没有for_each风格的语言中,开发者习惯于用索引(下标)来遍历一个容器中的元素。而在Python中,这种操作可以通过in关键字来更为优雅地实现。

不良风格:

1 my_list = ['Larry', 'Moe', 'Curly']
2 index = 0
3 while index < len(my_list):
4     print(my_list[index])
5     index += 1

地道Python:

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

1.2.3 使用else去执行一个for循环全部遍历结束后的代码

在Python的for循环中可以包含一个else分句,这是一个不多人知道的技巧。else语句块会在for循环中的迭代结束后执行,除非在迭代过程中循环因为break语句结束。利用这种写法我们可以在循环中执行条件检查。要么在要检查的条件语句为真时用break语句停止循环,要么在循环结束后进入else语句块并执行条件未被满足的情况下要执行的动作。这样做避免了在循环中单独使用一个标示变量来检查条件是否被满足。

不良风格:

 1 for user in get_all_users():
 2     has_malformed_email_address = False
 3     print('检查 {}'.format(user))
 4     for email_address in user.get_all_email_addresses():
 5         if email_is_malformed(email_address):
 6             has_malformed_email_address = True
 7             print('包含恶意email地址!')
 8             break
 9     if not has_malformed_email_address:
10         print('所有email地址均有效!')

地道Python:

1 for user in get_all_users():
2     print('检查 {}'.format(user))
3     for email_address in user.get_all_email_addresses():
4         if email_is_malformed(email_address):
5             print('包含恶意email地址!')
6             break
7     else:
8         print('所有email地址均有效!')

转载请注明出处:達聞西@博客園

下一篇:翻译《Writing Idiomatic Python》(二):函数、异常

原文地址:https://www.cnblogs.com/frombeijingwithlove/p/4392858.html