第 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 章会详细介绍列表。 ↩
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论