第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语句的执行流程如下:
- 计算条件表达式的值,判断是True或False。
- 如果为False,结束while语句并执行下一条语句。
- 如果为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章会详细介绍列表。 ↩