第5章 迭代

5.1 更新变量

赋值语句的常见模式是对变量进行更新,变量的新值依赖于旧值。

x = x+1

这条语句的作用是:得到x的当前值,加一,然后将相加结果作为新值赋予x。

如果更新一个不存在的变量,那么会出错。这是因为Python在给x赋值之前,首先计算等号右边:

>>> x = x+1
NameError: name 'x' is not defined

在更新变量之前,必须初始化,通常使用简单的赋值语句:

>>> x = 0
>>> x = x+1

通过加一操作更新变量称为增量更新,减一操作称为减量更新。

5.2 while语句

计算机经常用于执行一些重复性的任务。对计算机来说,执行相同或相似的任务而不出错,这很简单,但人就做不好。迭代很常见,Python提供了一些功能语句,使这类任务变得更加简单。

Python的一种迭代形式是while语句。下面是一个简单的例子,从5开始倒数,然后打印出“Blastoff!”。

n = 5
while n > 0:
    print n
    n = n-1
print 'Blastoff!'

几乎可以像读英文一样,读懂这条while语句。它的作用是:当n大于0时,显示n的值,然后对n减1。当n等于0时,结束while语句,显示“blastoff!”。

严格说, while语句的执行流程如下:

  1. 计算条件表达式的值,判断是True或False。
  2. 如果为False,结束while语句并执行下一条语句。
  3. 如果为True,执行while中的语句体,然后返回步骤1。

此类执行流程称为循环。执行到第三步又返回到顶部。每执行一次循环体,称为一次迭代。上面的循环,我们可以说“它进行了五次迭代”,表示循环体被执行了五次。

循环体会改变一个或多个变量的值。因此当条件不满足时,循环结束。有一种变量在每次循环执行时其值都会变化,并控制循环什么时候结束,这种变量称为迭代变量。如果没有迭代变量,循环就会永远执行下去,导致无限循环。

5.3 无限循环

对于程序员来说,无限循环的有趣实例就是洗发水的说明书,“泡沫,冲洗,重复”。这就是一个无限循环,没有迭代变量来表明什么时候结束这个循环。

上面的倒数例子中,循环的确是结束了。因为n值的个数是有限的,我们可以看到n值随着循环的执行不断减小,最终变为0。有些情况的循环明显是无限的,这是因为它根本就没有迭代变量。

5.4 “无限循环”与break语句

有时候循环运行到一半时,你还没意识到这时候该结束循环了。在这种情况下,你可以写一个无限的循环,然后使用break语句跳出循环。

下面的代码明显是一个无限循环,while语句的逻辑表达式是常量True:

n = 10
while True:
    print n,
    n = n - 1
print 'Done!'

如果犯了这个错误并且运行这个代码,你会很快学会如何停止一个正在运行的Python进程,或者找到计算机的关机按钮。由于循环顶部的逻辑表达式是常量True,所以循环条件一直都满足,这个程序会一直运行下去,直到计算机没电。

虽然这是一个不正常的无限循环,我们还是可以使用这种模式来建立有用的循环。只要将break语句放进循环体,明确退出条件及行为。当达到结束条件时,就可以结束循环。

举例来说,如果想要用户直到输入done时结束的话,代码可以这样写:

while True:
    line = raw_input('> ')
    if line == 'done':
        break
    print line
print 'Done!'

这个循环的条件是True且不会变,因此循环会一直执行下去,直到触发break语句。

每次执行这个循环,它都会提示用户一个尖括号。如果用户输入done,那么break语句就会结束这个循环。否则,这个程序会一直提示用户进行输入,回到顶部继续执行。下面是一个程序运行的结果演示:

> hello there
hello there
> finished
finished
> done
Done!

while语句的这种写法很常见。你可以在循环中的任何位置检查条件(不仅局限于顶部),并且可以主动定义停止条件(当发生什么就停止),而不是被动等待判断(一直运行直到发生什么)。

5.5 使用continue语句结束迭代

有时在循环的迭代中,你想要结束当前迭代,立刻进行下一轮迭代。在这种情况下,使用continue语句跳入下一轮迭代,无需完成当前迭代的循环体。

下面的循环例子不断打印输入值,直到用户输入“done”才会结束。但是,#号开头的输入不会被打印出来(这有点像Python的注释)。

运行一下这个加入了continue语句的新程序。

while True:
    line = raw_input('> ')
    if line[0] == '#' :
        continue
    if line == 'done':
        break
    print line
print 'Done!'

除了#号开头的行,其他所有的行都被打印出来。当continue语句被执行,当前迭代会结束,跳到while语句的开头执行下一轮循环,这样也就跳过了print语句。

5.6 使用for语句定义循环

有时候我们需要要遍历一组东西,例如,单词列表,文件的每一行或是一组数字。遍历一组东西,可以用for语句来构造循环。因为wile语句是简单地进行循环,直到条件变为False,我们称其为无限循环。与之不同的是,for语句是对已知的数据项集合进行循环,因此它的迭代次数取决于数据项的个数。

for循环和while循环的语法相似,下面是一个for语句和循环体代码示例:

friends = ['Joseph', 'Glenn', 'Sally']
for friend in friends:
    print 'Happy New Year:', friend
print 'Done!'

在Python语法中,friends变量是包含三个字符串的列表1。for循环遍历整个列表,依次打印每个字符串这三个字符,输出结果如下所示:

Happy New Year: Joseph
Happy New Year: Glenn
Happy New Year: Sally
Done!

如果用英语来解释这个for循环,就不如while循环的解释那么直接。你可以把它当做一个朋友名单,那么这段代码的作用是:对friends集合中的每个朋友执行for循环体。

观察这个for循环,for和in是Python的保留关键字,friend和friends是变量。

for friend in friends:

    print ’Happy New Year’, friend

具体来说,friend是for循环的迭代变量。变量friend的值在每次迭代时都会改变,并控制for循环什么时候结束。这个迭代变量取得friends中存储的三个字符串。

5.7 循环模式

我们经常使用for循环或while循环来遍历列表或文件的内容,还会通过浏览来寻找一组数值中的最大值或最小值。

此类循环的构造方法如下:

  • 循环开始之前初始化一个或多个变量。
  • 在循环体中对每个数据项进行计算,这样可能会改变循环体中变量的值。
  • 循环结束时查看最终变量。

我们会用一组数字来展示这些循环模式的理念和构造方法。

5.7.1 统计与求和循环

举例来说,为了统计一个列表的数据项个数, for循环编写如下:

count = 0
for itervar in [3, 41, 12, 9, 74, 15]:
    count = count + 1
print 'Count: ', count

循环开始之前将变量count的值设为0,然后用一个for循环来遍历数值列表。迭代变量命名为itervar。虽然我们并不在循环体中使用它,但它控制着循环,让循环体为每一个列表值执行一次。

在循环体中,列表的每个值都会导致count值加一。随着循环的执行,count值就是“当前”所看到的。

循环一旦结束,count值就等于列表中数值的个数。在循环最后,总数“落入我们手中”。通过构造这个循环,在循环结束时我们得到了想要的。

另一个相似的循环求出一组数值的总和:

total = 0
for itervar in [3, 41, 12, 9, 74, 15]:
    total = total + itervar
print 'Total: ', total

在这个循环中,我们用到了迭代变量。不是之前循环中为count变量简单加一,而是在每次循环中加上实际的数字(如3、41、12等)。total变量的作用是求出目前的累计值。在循环开始之前,由于还没有遇到任何值,所以total值是0。循环中会累加,total最终值是所有数字的总和。

随着循环的进行,total累积了列表各项的和。这样的变量有时候被称为累加器(accumulator)。

不管统计循环还是求和循环,在实际使用中都不是很有用。这是因为Python提供了内置函数len()和sum(),分别计算列表元素的个数和列表各项的总和。

5.7.2 最大值与最小值循环

找出列表或者序列中的最大值,构造如下循环:

largest = None
print 'Before:', largest
for itervar in [3, 41, 12, 9, 74, 15]:
    if largest is None or itervar > largest :
        largest = itervar
    print 'Loop:', itervar, largest
print 'Largest:', largest

程序执行结果如下:

Before: None
Loop: 3 3
Loop: 41 41
Loop: 12 41
Loop: 9 41
Loop: 74 74
Loop: 15 74
Largest: 74

largest变量是“目前我们看到的最大值”。在循环开始之前,将largest设为常量None。None是一个特殊的常量,表示变量为“空”。

在循环开始之前,我们没有遇到任何值,所以largest值为None。当循环开始执行,如果largest为None,则将第一个值作为目前看到的最大值。可以看到,第一轮迭代中itervar的值是3,largest的值是None,所以立即将largest的值变为3。

第一次迭代之后,最大值就不是None了。复合逻辑表达式的第二部分设置了触发器,检查 itervar值是否大于largest值。如果当前值大于largest值,就会将更大的新值赋予largest。从程序输出可以看出,largest值从3变为41,然后变为74。

循环结束后遍历了所有的值,largest值已经是整个列表中的最大值了。

计算最小值的代码仅需要做很小改动:

smallest = None
print 'Before:', smallest
for itervar in [3, 41, 12, 9, 74, 15]:
    if smallest is None or itervar < smallest:
        smallest = itervar
    print 'Loop:', itervar, smallest
print 'Smallest:', smallest

同样的,smallest值在循环前、循环中与循环后都是“目前看到的最小值”。循环结束后,smallest值就是整个列表的最小值。

统计和求和同样有Python内置函数支持,比如,使用max()函数和min()函数可以不用到循环代码。

下面是Python内置函数min()的简化版代码:

def min(values):
    smallest = None
    for value in values:
        if smallest is None or value < smallest:
            smallest = value
    return smallest

在这段代码中,我们移除了所有的print语句,与Python内置函数min()基本等价。

5.8 调试

当程序越写越长,你就会发现需要花费更多的时间来调试。代码越多意味着犯错的机会越大,隐藏的错误也就越多。

相反,试着将问题分为两部分。在程序的中间位置,寻找一处可验证的代码,插入一个print语句(或者其它可验证效果的语句),然后运行程序。

如果中间点检查出错,那么问题肯定出在程序的前半部分。如果中间点检查没错,问题就出在程序的后半部分。

每执行一次这样的检查,你就会把代码范围缩减一半。如果代码少于100行,进行6次之后,就只剩下一两行,理论上至少是这样。

实际上,“程序的中间位置”并不是很明显,也可能没法进行检查。统计行数然后除以2,找到精确的中间位置代码行,这种做法没有意义。一般做法是,考虑程序中容易出现错误的地方,进行检查。选择你认为极有可能会出错的位置前后,设置检查点。

5.9 术语

累加器:循环语句中用来累积结果的变量。

计数器:循环语句中用来计算发生次数的变量。计数器初始化为0,每次需要计数时,增加它的值。

减量:减少变量值的更新。

初始化:为随后会更新的变量赋予初始值的语句。

增量:增加变量值的更新(通常是加一)。

无限循环:终止条件无法达到或者没有终止条件的循环。

迭代:通过递归函数调用或者循环来重复执行一组语句。

5.10 练习

习题5.1 编写一个程序,重复读取数据,直到用户输入“done”。一旦输入“done”,打印总和、个数与平均值。如果用户输入的不是数字,使用try和except捕获异常,打印错误信息,然后跳过继续执行循环。

习题5.2 编写一个程序,提示用户输入一组数字,输出最大值和最小值,不要求平均值。

1. 第8章会详细介绍列表。