8.1 if 语句

if 语句的语法如下:

if expression:
    expr_true_suite
if expression:
    expr_true_suite

8.1.1 多重条件表达式

单个 if 语句可以通过使用布尔操作符 and , or 和 not实现多重判断条件或是否定判断条件.

if not warn and (system_load >= 10):
    print "WARNING: losing resources"
if not warn and (system_load >= 10):
    print "WARNING: losing resources"

8.2 else 语句

if expression:
    expr_true_suite
else:
    expr_false_suite
if expression:
    expr_true_suite
else:
    expr_false_suite

8.2.1 避免“悬挂 else”

缩进避免了语法上正确的代码中存在潜在的问ti. 其中一个就是"悬挂 else (dangling else)" 。

Python 的缩进使用强制使代码正确对齐, 决定 else 属于哪一个 if . 限制您的选择从而减少了不确定性。

8.3 elif (即 else-if )语句

if expression1:
    expr1_true_suite
elif expression2:
    expr2_true_suite
elif expressionN:
    exprN_true_suite
else:
    none_of_the_above_suite
if expression1:
    expr1_true_suite
elif expression2:
    expr2_true_suite
elif expressionN:
    exprN_true_suite
else:
    none_of_the_above_suite

switch/case 语句的替代品么?

将来, Python 可能会支持 switch /case , 但是可以用其他的 Python结构来模拟它. 

if user.cmd == 'create':
    action = "create item"
elif user.cmd == 'delete':
    action = 'delete item'
elif user.cmd == 'update':
    action = 'update item'
else:
    action = 'invalid choice... try again!'
if user.cmd == 'create':
    action = "create item"
elif user.cmd == 'delete':
    action = 'delete item'
elif user.cmd == 'update':
    action = 'update item'
else:
    action = 'invalid choice... try again!'

上面的语句完全可以满足我们的需要,不过我们还可以用序列和成员关系操作符来简化它:

if user.cmd in ('create', 'delete', 'update'):
    action = '%s item' % user.cmd
else:
    action = 'invalid choice... try again!'
if user.cmd in ('create', 'delete', 'update'):
    action = '%s item' % user.cmd
else:
    action = 'invalid choice... try again!'

可以用 Python 字典给出更加优雅的解决方案, 将在第七章 "映射和集合类型"中介绍字典.

msgs = {'create': 'create item','delete': 'delete item','update': 'update item'}
default = 'invalid choice... try again!'
action = msgs.get(user.cmd, default)
msgs = {'create': 'create item','delete': 'delete item','update': 'update item'}
default = 'invalid choice... try again!'
action = msgs.get(user.cmd, default)

众所周知, 使用映射对象(比如字典)的一个最大好处就是它的搜索操作比类似 if-elif-else语句或是 for 循环这样的序列查询要快很多.

8.4 条件表达式(即"三元操作符")

 X if C else Y .有了三元运算符后你就只需要一行完成条件判断和赋值操作,在 2.5 以前的版本中,程序员最多这样做(其实是一个 hack ):

>>> smaller = (x < y and [x] or [y])[0]
>>> smaller
3
```
>>> smaller = (x < y and [x] or [y])[0]
>>> smaller
3
```

在 2.5 和更新的版本中, 你可以使用更简明的条件表达式:

>>> smaller = x if x < y else y
>>> smaller
3
>>> smaller = x if x < y else y
>>> smaller
3

8.5 while 语句

8.5.1 一般语法
while expression:
    suite_to_repeat
while expression:
    suite_to_repeat

8.5.2 计数循环
count = 0
while (count < 9):
    print 'the index is:', count
    count += 1
count = 0
while (count < 9):
    print 'the index is:', count
    count += 1
8.5.3 无限循环

"无限"的循环不一定是坏事, 许多通讯服务器的客户端/服务器系统就是通过它来工作的. 

while True:
    handle, indata = wait_for_client_connect()
    outdata = process_request(indata)
    ack_result_to_client(handle, outdata)
while True:
    handle, indata = wait_for_client_connect()
    outdata = process_request(indata)
    ack_result_to_client(handle, outdata)

例如上边的代码就是故意被设置为无限循环的. 这是因为服务器代码是用来等待客户端(可能通过网络)来连接的. 客户端向服务器发送请求, 服务器处理请求.请求被处理后, 服务器将向客户端返回数据, 而此时客户端可能断开连接或是发送另一个请求.对于服务器而言它已经完成了对这个客户端的任务, 它会返回最外层循环等待下一个连接. 在第16 章, "网络编程" 和第 17 章节 "Internet 客户端编程" 里你将了解关于如何处理客户端/服务器的更多信息.

8.6 for 语句

它可以遍历序列成员, 可以用在列表解析生成器表达式中, 它会自动地调用迭代器的next()方法, 捕获 StopIteration 异常并结束循环(所有这一切都是在内部发生的).

8.6.1 一般语法

for 循环会访问一个可迭代对象(例如序列或是迭代器)中的所有元素, 并在所有条目都处理过后结束循环. 它的语法如下:

for iter_var in iterable:
    suite_to_repeat
for iter_var in iterable:
    suite_to_repeat

每次循环, iter_var 迭代变量被设置为可迭代对象(序列, 迭代器, 或者是其他支持迭代的对象)的当前元素

8.6.2 用于序列类型

本节中, 我们将学习用 for 循环迭代不同的序列对象. 样例将涵盖字符串, 列表, 以及元组.

>>> for eachLetter in 'Names':
... print 'current letter:', eachLetter
...
current letter: N
current letter: a
current letter: m
current letter: e
current letter: s
>>> for eachLetter in 'Names':
... print 'current letter:', eachLetter
...
current letter: N
current letter: a
current letter: m
current letter: e
current letter: s

迭代序列有三种基本方法:

(1)通过序列项迭代

>>> nameList = ['Walter', "Nicole", 'Steven', 'Henry']
>>> for eachName in nameList:
... print eachName, "Lim"
...
Walter Lim
Nicole Lim
Steven Lim
Henry Lim
>>> nameList = ['Walter', "Nicole", 'Steven', 'Henry']
>>> for eachName in nameList:
... print eachName, "Lim"
...
Walter Lim
Nicole Lim
Steven Lim
Henry Lim

===通过序列索引迭代===

(2)另个方法就是通过序列的索引来迭代:

>>> nameList = ['Cathy', "Terry", 'Joe', 'Heather','Lucy']
>>> for nameIndex in range(len(nameList)):
... print "Liu,", nameList[nameIndex]
...
Liu, Cathy
Liu, Terry
Liu, Joe
Liu, Heather
Liu, Lucy
>>> nameList = ['Cathy', "Terry", 'Joe', 'Heather','Lucy']
>>> for nameIndex in range(len(nameList)):
... print "Liu,", nameList[nameIndex]
...
Liu, Cathy
Liu, Terry
Liu, Joe
Liu, Heather
Liu, Lucy

这里我们使用了内建的 len() 函数获得序列长度, 使用 range() 函数(我们将在下面详细讨论它)创建了要迭代的序列.

如果你对性能有所了解的话, 那么毫无疑问你会意识到直接迭代序列要比通过索引迭代快. 如果你不明白, 那么你可以仔细想想.

===使用项和索引迭代===

(3)两全其美的办法是使用内建的 enumerate() 函数:

>>> nameList = ['Donn', 'Shirley', 'Ben', 'Janice',
... 'David', 'Yen', 'Wendy']
>>> for i, eachLee in enumerate(nameList):
... print "%d %s Lee" % (i+1, eachLee)
...
1 Donn Lee
2 Shirley Lee
3 Ben Lee
4 Janice Lee
5 David Lee
6 Yen Lee
7 Wendy Lee
>>> nameList = ['Donn', 'Shirley', 'Ben', 'Janice',
... 'David', 'Yen', 'Wendy']
>>> for i, eachLee in enumerate(nameList):
... print "%d %s Lee" % (i+1, eachLee)
...
1 Donn Lee
2 Shirley Lee
3 Ben Lee
4 Janice Lee
5 David Lee
6 Yen Lee
7 Wendy Lee

8.6.3 用于迭代器类型

用 for 循环访问迭代器和访问序列的方法差不多. 唯一的区别就是 for 语句会为你做一些额外的事情. 迭代器并不代表循环条目的集合.迭代器对象有一个 next() 方法, 调用后返回下一个条目. 所有条目迭代完后, 迭代器引发一个 StopIteration 异常告诉程序循环结束. for 语句在内部调用 next() 并捕获异常.

使用迭代器做 for 循环的代码与使用序列条目几乎完全相同. 事实上在大多情况下, 你无法分辨出你迭代的是一个序列还是迭代器, 因此,这就是为什么我们在说要遍历一个迭代器时,实际上可能我们指的是要遍历一个序列,迭代器,或是一个支持迭代的对象(它有next()方法)。

8.6.4 range() 内建函数

=== range() 的完整语法===

Python 提供了两种不同的方法来调用 range() . 完整语法要求提供两个或三个整数参数:

range(start, end, step =1)
range(start, end, step =1)

range() 会返回一个包含所有 k 的列表, 这里 start <= k < end , 从 start 到 end , k 每次递增 step . step 不可以为零,否则将发生错误.step默认为1.

>>> range(2, 19, 3)
[2, 5, 8, 11, 14, 17]
>>> range(2, 19, 3)
[2, 5, 8, 11, 14, 17]

===range() 简略语法===

range() 还有两种简略的语法格式:

range(end)
range(start, end) # start 默认为 0 , step 默认为 1
range(end)
range(start, end) # start 默认为 0 , step 默认为 1

核心笔记: 为什么 range() 不是只有一种语法?

你已经知道了 range() 的所有语法, 有些人可能会问一个挑剔的问ti, 为什么不把这两种语法合并成一个下面这样的语法?

range(start=0, end, step =1) # 错误

这个语法不可以使用两个参数调用. 因为 step 要求给定 start . 换句话说, 你不能只传递end 和 step 参数. 因为它们会被解释器误认为是 start 和 end .

8.6.5 xrange() 内建函数

xrange() 类似 range() , 不过当你有一个很大的范围列表时, xrange() 可能更为适合, 因为它不会在内存里创建列表的完整拷贝. 它只被用在 for 循环中, 在 for 循环外使用它没有意义。

同样地, 你可以想到, 它的性能远高出 range(), 因为它不生成整个列表。在 Python 的将来版本中, range() 可能会像 xrange() 一样, 返回一个可迭代对象(不是列表也不是一个迭代器) - 它会像前边一章讨论的那样.

8.6.6 与序列相关的内建函数

sorted(), reversed(), enumerate(), zip()

下边是使用循环相关和序列相关函数的例子. 为什么它们叫"序列相关"呢? 是因为其中两个函数( sorted() 和 zip() )返回一个序列(列表), 而另外两个函数( reversed() 和 enumerate() )返回迭代器(类似序列)

>>> albums = ('Poe', 'Gaudi', 'Freud', 'Poe2')
>>> years = (1976, 1987, 1990, 2003)
>>> for album in sorted(albums):
... print album,
...
Freud Gaudi Poe Poe2
>>> for album in reversed(albums):
... print album,
...
Poe2 Freud Gaudi Poe
>>> for i, album in enumerate(albums):
... print i, album
...
0 Poe
1 Gaudi
2 Freud
3 Poe2
>>> for album, yr in zip(albums, years):
... print yr, album
...

1976 Poe
1987 Gaudi
1990 Freud
2003 Poe2
>>> albums = ('Poe', 'Gaudi', 'Freud', 'Poe2')
>>> years = (1976, 1987, 1990, 2003)
>>> for album in sorted(albums):
... print album,
...
Freud Gaudi Poe Poe2
>>> for album in reversed(albums):
... print album,
...
Poe2 Freud Gaudi Poe
>>> for i, album in enumerate(albums):
... print i, album
...
0 Poe
1 Gaudi
2 Freud
3 Poe2
>>> for album, yr in zip(albums, years):
... print yr, album
...

1976 Poe
1987 Gaudi
1990 Freud
2003 Poe2

8.7 break 语句

类似 C 中的传统 break .常用在当某个外部条件被触发(一般通过 if 语句检查), 需要立即从循环中退出时. break 语句可以用在 while 和 for 循环中.

8.8 continue 语句

核心笔记: continue 语句

当遇到continue 语句时, 程序会终止当前循环,并忽略剩余的语句, 然后回到循环的顶端. 在开始下一次迭代前,如果是条件循环, 将验证条件表达式.如果是迭代循环,将验证是否还有元素可以迭代. 只有在验证成功的情况下, 才会开始下一次迭代.

while 循环是条件性的, 而 for 循环是迭代的, 所以 continue 在开始下一次循环前要满足一些先决条件, 否则循环会正常结束。

valid = False
count = 3
passwdList = ["admin1", "admin2", "admin3"]
while count > 0:
    inputPasswd = input("enter password")
    # check for valid passwd
    for eachPasswd in passwdList:
        if inputPasswd == eachPasswd:
            valid = True
            break
    if not valid:
        print ("invalid input")
        count -= 1
        continue
    else:
        break
valid = False
count = 3
passwdList = ["admin1", "admin2", "admin3"]
while count > 0:
    inputPasswd = input("enter password")
    # check for valid passwd
    for eachPasswd in passwdList:
        if inputPasswd == eachPasswd:
            valid = True
            break
    if not valid:
        print ("invalid input")
        count -= 1
        continue
    else:
        break

验证用户输入. 用户有三次机会来输入正确的密码, 如果失败, 那么 valid 变量将仍为一个布尔假( 0 ), 然后我们可以采取必要的操作阻止用户猜测密码.

8.9 pass 语句

Python 还提供了 pass 语句. 有些地方在语法上要求要有代码, 而Python 中没有对应的空大括号或是分号( ; )来表示C 语言中的 "不做任何事". 因此, Python 提供了 pass 语句, 它不做任何事情,pass 同样也可作为开发中的小技巧, 标记你后来要完成的代码, 例如这样:

def foo_func():
    pass
或是
if user_choice == 'do_calc':
    pass 
else:
    pass
def foo_func():
    pass
或是
if user_choice == 'do_calc':
    pass 
else:
    pass

这样的代码结构在开发和调试时很有用, 因为编写代码的时候你可能要先把结构定下来,但你不希望它干扰其他已经完成的代码. 在不需要它做任何事情地方, 放一个pass 将是一个很好的主意.

另外它在异常处理中也被经常用到, 我们将在第 10 章中详细介绍; 比如你跟踪到了一个非致命的错误, 不想采取任何措施(你只是想记录一下事件或是在内部进行处理).

8.10 再谈 else 语句

Python 和C语言不同,可以在 while 和 for 循环中使用 else 语句. 它们是怎么工作的呢? 在循环中使用时, else子句只在循环完成后执行, 也就是说 break 语句也会跳过 else 块.

(maxFact.py) 利用这个语法完成了 showMaxFactor() 函数.这个程序显示出 10 到 20 中的数字的最大约数. 该脚本也会提示这个数是否为素数.

def showMaxFactor(num):
    count = num/2
    while count > 1:
        if num%count == 0:
            print ("larger factor of %d is %d" % (num,count))
            break
        count-=1
    else:
        print (num, "is prime")

for eachNum in range(10, 21):
    showMaxFactor(eachNum)
def showMaxFactor(num):
    count = num/2
    while count > 1:
        if num%count == 0:
            print ("larger factor of %d is %d" % (num,count))
            break
        count-=1
    else:
        print (num, "is prime")

for eachNum in range(10, 21):
    showMaxFactor(eachNum)

同样地, for 循环也可以有 else 用于循环后处理. 它和 while 循环中的else 处理方式相同. 只要for 循环是正常结束的(不是通过 break ), else 子句就会执行.

表 8.1 条件及循环语句中的辅助语句总结

a. pass 在任何需要语句块(一个或多个语句)的地方都可以使用(例如 elif , else , clasa ,def , try , except , finally ).

8.11 迭代器和 iter() 函数

8.11.1 什么是迭代器?

为类序列对象提供了一个类序列的接口. 我们已经介绍过序列. 它们是一组数据结构。Python 的迭代无缝地支持序列对象, 而且它还允许程序员迭代非序列类型, 包括用户定义的对象.迭代器用起来很灵巧, 你可以迭代不是序列但表现出序列行为的对象, 例如字典的 key , 一个文件的行, 等等. 当你使用循环迭代一个对象条目时, 你几乎不可能分辨出它是迭代器还是序列.你不必去关注这些, 因为 Python 让它象一个序列那样操作.

8.11.2 为什么要迭代器?

援引 PEP (234) 中对迭代器的定义:

.. 提供了可扩展的迭代器接口.

.. 对列表迭代带来了性能上的增强.

.. 在字典迭代中性能提升.

.. 创建真正的迭代接口, 而不是原来的随机对象访问.

.. 与所有已经存在的用户定义的类以及扩展的模拟序列和映射的对象向后兼容

.. 迭代非序列集合(例如映射和文件)时, 可以创建更简洁可读的代码.

8.11.3 如何迭代?

根本上说, 迭代器就是有一个 next() 方法的对象, 而不是通过索引来计数. 当你或是一个循环机制(例如 for 语句)需要下一个项时, 调用迭代器的 next() 方法就可以获得它. 条目全部取出后, 会引发一个 StopIteration 异常, 这并不表示错误发生, 只是告诉外部调用者, 迭代完成.不过, 迭代器也有一些限制. 例如你不能向后移动, 不能回到开始, 也不能复制一个迭代器.如果你要再次(或者是同时)迭代同个对象, 你只能去创建另一个迭代器对象.

reversed() 内建函数将返回一个反序访问的迭代器. enumerate() 内建函数同样也返回迭代器.另外两个新的内建函数, any() 和 all() , 如果迭代器中某个/所有条目的值都为布尔真时,则它们返回值为真. 本章先前部分我们展示了如何在 for 循环中通过索引或是可迭代对象来遍历条目. 同时 Python 还提供了一整个 itertools 模块, 它包含各种有用的迭代器.

8.11.4 使用迭代器

===序列===

正如先前提到的, 迭代 Python 的序列对象和你想像的一样:

>>> myTuple = (123, 'xyz', 45.67)
>>> i = iter(myTuple)
>>> i.next()
123
>>> i.next()
'xyz'
>>> i.next()
45.67
>>> i.next()
Traceback (most recent call last):
File "", line 1, in ?
StopIteration
>>> myTuple = (123, 'xyz', 45.67)
>>> i = iter(myTuple)
>>> i.next()
123
>>> i.next()
'xyz'
>>> i.next()
45.67
>>> i.next()
Traceback (most recent call last):
File "", line 1, in ?
StopIteration

如果这是一个实际应用程序, 那么我们需要把代码放在一个 try-except 块中. 序列现在会自动地产生它们自己的迭代器, 所以一个 for 循环:

for i in seq:
    do_something_to(i)
for i in seq:
    do_something_to(i)

实际上是这样工作的:

fetch = iter(seq)
while True:
    try:
        i = fetch.next()
    except StopIteration:
        break
    do_something_to(i)
fetch = iter(seq)
while True:
    try:
        i = fetch.next()
    except StopIteration:
        break
    do_something_to(i)

不过, 你不需要改动你的代码, 因为 for 循环会自动调用迭代器的 next() 方法(以及监视StopIteration 异常).

===字典===

字典和文件是另外两个可迭代的 Python 数据类型. 字典的迭代器会遍历它的键(keys).语句 for eachKey in myDict.keys() 可以缩写为 for eachKey in myDict , 例如:

>>> legends = { ('Poe', 'author'): (1809, 1849, 1976),... ('Gaudi', 'architect'): (1852, 1906, 1987),... ('Freud', 'psychoanalyst'): (1856, 1939, 1990)... }
...
>>> for eachLegend in legends:
... print 'Name: %s\tOccupation: %s' % eachLegend
... print ' Birth: %s\tDeath: %s\tAlbum: %s\n' % legends[eachLegend]
...
Name: Freud Occupation: psychoanalyst
Birth: 1856 Death: 1939 Album: 1990
Name: Poe Occupation: author
Birth: 1809 Death: 1849 Album: 1976
Name: Gaudi Occupation: architect
Birth: 1852 Death: 1906 Album: 1987
>>> legends = { ('Poe', 'author'): (1809, 1849, 1976),... ('Gaudi', 'architect'): (1852, 1906, 1987),... ('Freud', 'psychoanalyst'): (1856, 1939, 1990)... }
...
>>> for eachLegend in legends:
... print 'Name: %s\tOccupation: %s' % eachLegend
... print ' Birth: %s\tDeath: %s\tAlbum: %s\n' % legends[eachLegend]
...
Name: Freud Occupation: psychoanalyst
Birth: 1856 Death: 1939 Album: 1990
Name: Poe Occupation: author
Birth: 1809 Death: 1849 Album: 1976
Name: Gaudi Occupation: architect
Birth: 1852 Death: 1906 Album: 1987

另外, Python 还引进了三个新的内建字典方法来定义迭代: myDict.iterkeys() (通过 keys 迭代), myDict.itervalues() (通过 values 迭代), 以及 myDicit.iteritems() (通过 key/value 对来迭代). 注意, in 操作符也可以用于检查字典的 key 是否存在 , 之前的布尔表达式myDict.has_key(anyKey) 可以被简写为 anyKey in myDict .

===文件===

文件对象生成的迭代器会自动调用 readline() 方法. 这样, 循环就可以访问文本文件的所有行. 程序员可以使用 更简单的 for eachLine in myFile 替换 for eachLine in myFile.readlines() :

>>> myFile = open('config-win.txt')
>>> for eachLine in myFile:
... print eachLine, # comma suppresses extra \n
...
[EditorWindow]
font-name: courier new
font-size: 10
>>> myFile.close()
>>> myFile = open('config-win.txt')
>>> for eachLine in myFile:
... print eachLine, # comma suppresses extra \n
...
[EditorWindow]
font-name: courier new
font-size: 10
>>> myFile.close()

8.11.5 可变对象和迭代器

记住,在迭代可变对象的时候修改它们并不是个好主意. 这在迭代器出现之前就是一个问题.一个流行的例子就是循环列表的时候删除满足(或不满足)特定条件的项:

for eachURL in allURLs:
    if not eachURL.startswith('http://'):
        allURLs.remove(eachURL) # YIKES!!
for eachURL in allURLs:
    if not eachURL.startswith('http://'):
        allURLs.remove(eachURL) # YIKES!!

除列表外的其他序列都是不可变的, 所以危险就发生在这里. 一个序列的迭代器只是记录你当前到达第多少个元素, 所以如果你在迭代时改变了元素, 更新会立即反映到你所迭代的条目上.在迭代字典的 key 时, 你绝对不能改变这个字典. 使用字典的 keys() 方法是可以的, 因为keys() 返回一个独立于字典的列表. 而迭代器是与实际对象绑定在一起的, 它将不会继续执行下去:

>>> myDict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> for eachKey in myDict:
...     print eachKey, myDict[eachKey]
...     del myDict[eachKey]
... a 1
Traceback (most recent call last):
File "", line 1, in ?
RuntimeError: dictionary changed size during iteration
>>> myDict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> for eachKey in myDict:
...     print eachKey, myDict[eachKey]
...     del myDict[eachKey]
... a 1
Traceback (most recent call last):
File "", line 1, in ?
RuntimeError: dictionary changed size during iteration

这样可以避免有缺陷的代码. 更多有关迭代器的细节请参阅 PEP 234 .

8.11.6 如何创建迭代器

对一个对象调用 iter() 就可以得到它的迭代器. 它的语法如下:

iter(obj)
iter(func, sentinel )
iter(obj)
iter(func, sentinel )

如果你传递一个参数给 iter() , 它会检查你传递的是不是一个序列, 如果是, 那么很简单:根据索引从 0 一直迭代到序列结束. 另一个创建迭代器的方法是使用类, 我们将在第 13 章详细介绍, 一个实现了 __iter__() 和 next() 方法的类可以作为迭代器使用.如果是传递两个参数给 iter() , 它会重复地调用 func , 直到迭代器的下个值等于sentinel .

8.12 列表解析

列表解析来自函数式编程语言 Haskell . 可以用来动态地创建列表. 在第 11 章, 函数中, 我们函数式编程特性, 例如 lambda , map() ,以及 filter() 等。

通过列表解析 , 它们可以被简化为一个列表解析式子. map() 对所有的列表成员应用一个操作, filter() 基于一个条件表达式过滤列表成员. 最后, lambda 允许你快速地创建只有一行的函数对象:

[expr for iter_var in iterable]
[expr for iter_var in iterable]

这个语句的核心是 for 循环, 它迭代 iterable 对象的所有条目. 前边的 expr 应用于序列的每个成员, 最后的结果值是该表达式产生的列表. 

计算序列成员的平方的 lambda 函数表达式:

>>> map(lambda x: x ** 2, range(6))
[0, 1, 4, 9, 16, 25]
>>> map(lambda x: x ** 2, range(6))
[0, 1, 4, 9, 16, 25]

我们可以使用下面这样的列表解析来替换它:

>>> [x ** 2 for x in range(6)] # 你也可以用括号包住表达式, 像 [(x ** 2) for x in range(6)]
[0, 1, 4, 9, 16, 25]
>>> [x ** 2 for x in range(6)] # 你也可以用括号包住表达式, 像 [(x ** 2) for x in range(6)]
[0, 1, 4, 9, 16, 25]

结合if语句,列表解析还提供了一个扩展版本的语法:

[expr for iter_var in iterable if cond_expr]
# 这个语法在迭代时会过滤/捕获满足条件表达式 cond_expr 的序列成员.
[expr for iter_var in iterable if cond_expr]
# 这个语法在迭代时会过滤/捕获满足条件表达式 cond_expr 的序列成员.

判断一个数值对象是奇数还是偶数(奇数返回 1 , 偶数返回 0 ), 使用 filter() 和 lambda 挑选出序列中的奇数:

>>> seq = [11, 10, 9, 9, 10, 10, 9, 8, 23, 9, 7, 18, 12, 11, 12]
>>> filter(lambda x: x % 2, seq)
[11, 9, 9, 9, 23, 9, 7, 11]
>>> seq = [11, 10, 9, 9, 10, 10, 9, 8, 23, 9, 7, 18, 12, 11, 12]
>>> filter(lambda x: x % 2, seq)
[11, 9, 9, 9, 23, 9, 7, 11]

使用列表解析,获得想要的数字:

>>> [x for x in seq if x % 2]
[11, 9, 9, 9, 23, 9, 7, 11]
>>> [x for x in seq if x % 2]
[11, 9, 9, 9, 23, 9, 7, 11]

===矩阵样例===

你需要迭代一个有三行五列的矩阵么? 很简单:

>>> [(x+1,y+1) for x in range(3) for y in range(5)]
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2,
3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]

===磁盘文件样例===

假设我们有如下这样一个数据文件 hhga.txt , 需要计算出所有非空白字符的数目:

And the Lord spake, saying, "First shalt thou take out the Holy Pin. Then shalt thou count to three, no more, no less. Three shall be the number thou shalt count, and the number of the counting shall be three. Four shalt thou not count, nei- ther count thou two, excepting that thou then proceed to three. Five is right out. Once the number three, being the third number, be reached, then lobbest thou thy Holy Hand Grenade of Antioch towards thy foe, who, being naughty in My sight, shall snuff it."

我们已经知道可以通过 for line in data 迭代文件内容, 我们还可以把每行分割( split )为单词:

>>> f = open('hhga.txt', 'r')
>>> len([word for line in f for word in line.split()])
91
>>> f = open('hhga.txt', 'r')
>>> len([word for line in f for word in line.split()])
91

快速地计算文件大小

import os
>>> os.stat('hhga.txt').st_size
499L
import os
>>> os.stat('hhga.txt').st_size
499L

假定文件中至少有一个空白字符, 我们知道文件中有少于 499 个非空字符. 我们可以把每个单词的长度加起来, 得到和.

>>> f.seek(0)
>>> sum([len(word) for line in f for word in line.split()])
408
>>> f.seek(0)
>>> sum([len(word) for line in f for word in line.split()])
408

这里我们用 seek() 函数回到文件的开头, 因为迭代器已经访问完了文件的所有行. 一个清晰明了的列表解析完成了之前需要许多行代码才能完成的工作! 如你所见, 列表解析支持多重嵌套for 循环以及多个 if 子句. 完整的语法可以在官方文档中找到. 你也可以在 PEP 202 中找到更多关于列表解析的资料.

8.13 生成器表达式

生成器表达式是列表解析的一个扩展. 生成器是特定的函数, 允许你返回一个值, 然后"暂停"代码的执行, 稍后恢复. 我们将在第 11 章中讨论生成器.列表解析的一个不足就是必须要生成所有的数据, 用以创建整个列表. 这可能对有大量数据的迭代器有负面效应. 生成器表达式通过结合列表解析和生成器解决了这个问ti.生成器表达式与列表解析非常相似,而且它们的基本语法基本相同;

不过它并不真正创建数字列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目“产生”(yield)出来. 生成器表达式使用了"延迟计算"(lazy evaluation), 所以它在使用内存上更有效. 我们来看看它和列表解析到底有多相似:

列表解析:
[expr for iter_var in iterable if cond_expr]
生成器表达式:
(expr for iter_var in iterable if cond_expr)
列表解析:
[expr for iter_var in iterable if cond_expr]
生成器表达式:
(expr for iter_var in iterable if cond_expr)

生成器并不会让列表解析废弃, 它只是一个内存使用更友好的结构, 基于此, 有很多使用生成器地方. 下面我们提供了一些使用生成器表达式的例子。

===磁盘文件样例===

 计算文本文件中非空白字符总和例子中,如果这个文件的大小变得很大, 那么这行代码的内存性能会很低, 因为我们要创建一个很长的列表用于存放单词的长度.为了避免创建庞大的列表, 我们可以使用生成器表达式来完成求和操作. 它会计算每个单词的长度然后传递给 sum() 函数(它的参数不仅可以是列表,还可以是可迭代对象,比如生成器表达式).

这样, 我们可以得到优化后的代码(代码长度, 还有执行效率都很高效):

>>> sum(len(word) for line in data for word in line.split())
408
>>> sum(len(word) for line in data for word in line.split())
408

我们所做的只是把方括号删除: 少了两字节, 而且更节省内存 ... 非常地环保!

=== 交叉配对例子 ===

生成器表达式就好像是懒惰的列表解析. 它还可以用来处理其他列表或生成器, 例如这里的 rows 和 cols :

rows = [1, 2, 3, 17]
def cols(): 
    yield 56
    yield 2
    yield 1
rows = [1, 2, 3, 17]
def cols(): 
    yield 56
    yield 2
    yield 1

不需要创建新的列表, 直接就可以创建配对. 我们可以使用下面的生成器表达式:

x_product_pairs = ((i, j) for i in rows for j in cols())
现在我们可以循环 x_product_pairs , 它会懒惰地循环 rows 和 cols :
>>> for pair in x_product_pairs:
... print pair
...
(1, 56)
(1, 2)
(1, 1)
(2, 56)
(2, 2)
(2, 1)
(3, 56)
(3, 2)
(3, 1)
(17, 56)
(17, 2)
(17, 1)
x_product_pairs = ((i, j) for i in rows for j in cols())
现在我们可以循环 x_product_pairs , 它会懒惰地循环 rows 和 cols :
>>> for pair in x_product_pairs:
... print pair
...
(1, 56)
(1, 2)
(1, 1)
(2, 56)
(2, 2)
(2, 1)
(3, 56)
(3, 2)
(3, 1)
(17, 56)
(17, 2)
(17, 1)

=== 重构样例 ===

我们通过一个寻找文件最长的行的例子来看看如何改进代码. 在以前, 我们这样读取文件:

f = open('AAA', 'r')
longest = 0
while True:
    linelen = len(f.readline().strip())
    if not linelen:
        break
    if linelen > longest:
        longest = linelen
f.close()
return longest
f = open('AAA', 'r')
longest = 0
while True:
    linelen = len(f.readline().strip())
    if not linelen:
        break
    if linelen > longest:
        longest = linelen
f.close()
return longest

从那时起, 我们认识到如果读取了所有的行, 那么应该尽早释放文件资源. 如果这是一个很多进程都要用到的日志文件, 那么理所当然我们不能一直拿着它的句柄不释放. 是的, 我们的例子是用来展示的, 但是你应该得到这个理念. 所以读取文件的行的首选方法应该是这样:

f = open('/etc/motd', 'r')
longest = 0
allLines = f.readlines()
f.close()
for line in allLines:
    linelen = len(line.strip())
    if linelen > longest:
        longest = linelen
return longest
f = open('/etc/motd', 'r')
longest = 0
allLines = f.readlines()
f.close()
for line in allLines:
    linelen = len(line.strip())
    if linelen > longest:
        longest = linelen
return longest

列表解析允许我们稍微简化我们代码, 而且我们可以在得到行的集合前做一定的处理. 在下段代码中, 除了读取文件中的行之外,我们还调用了字符串的 strip() 方法处理行内容.

f = open('/etc/motd', 'r')
longest = 0
allLines = [x.strip() for x in f.readlines()]
f.close()
for line in allLines:
    linelen = len(line)
    if linelen > longest:
        longest = linelen
return longest
f = open('/etc/motd', 'r')
longest = 0
allLines = [x.strip() for x in f.readlines()]
f.close()
for line in allLines:
    linelen = len(line)
    if linelen > longest:
        longest = linelen
return longest

在处理大文件时候都有问ti, 因为 readlines() 会读取文件的所有行. 后来我们有了迭代器, 文件本身就成为了它自己的迭代器, 不需要调用 readlines() 函数. 我们已经做到了这一步, 为什么不去直接获得行长度的集合呢(之前我们得到的是行的集合)? 这样, 我们就可以使用 max() 内建函数得到最长的字符串长度:

f = open('/etc/motd', 'r')
allLineLens = [len(x.strip()) for x in f]
f.close()
return max(allLineLens)
f = open('/etc/motd', 'r')
allLineLens = [len(x.strip()) for x in f]
f.close()
return max(allLineLens)

你一行一行迭代 f 的时候, 列表解析需要文件的所有行读取到内存中,然后生成列表. 我们可以进一步简化代码: 使用生成器表达式替换列表解析, 然后把它移到 max()函数里, 这样, 所有的核心部分只有一行:

f = open('/etc/motd', 'r')
longest = max(len(x.strip()) for x in f)
f.close()
return longest
f = open('/etc/motd', 'r')
longest = max(len(x.strip()) for x in f)
f.close()
return longest

最后, 我们可以去掉文件打开模式(默认为读取), 然后让 Python 去处理打开的文件.

return max(len(x.strip()) for x in open('/etc/motd'))

生成器表达式在 Python 2.4 中被加入, 你可以在 PEP 289 中找到更多相关内容.

8.14 R 相关模块

Python 2.2 引进了迭代器, 在下一个发行 (2.3) 中, itertools 模块被加入, 用来帮助那些发现迭代器威力但又需要一些辅助工具的开发者