0.6. PY4E - Python for Everybody¶
切换导航
第 1 章:简介 第 2 章:变量 第 3 章:条件语句 第 4 章:函数 第 5 章:迭代 第 6 章:字符串 第 7 章:文件 第 8 章:列表 第 9 章:字典 第 10 章:元组 第 11 章:正则表达式 第 12 章:网络程序 第 13 章:Python 和网络服务 第 14 章:Python 对象 第 15 章:Python 和数据库 第 16 章:数据可视化
0.6.1. 迭代¶
0.6.1.1. 更新变量¶
赋值语句中常见的一种模式是更新变量的赋值语句,其中该变量的新值取决于旧值。
x = x + 1
这意味着“获取``x``的当前值,加 1,然后用新值更新``x``”。
如果您尝试更新一个不存在的变量,您会收到一个错误,因为 Python 在将值赋给 x 之前会先评估右侧:
>>> x = x + 1
NameError: name 'x' is not defined
在更新变量之前,您必须对其进行 初始化,通常使用简单的赋值:
>>> x = 0
>>> x = x + 1
通过加 1 来更新变量被称为 递增;减 1 被称为 递减。
0.6.1.2. while 语句¶
计算机常被用于自动化重复性任务。重复执行相同或相似的任务且不出错是计算机擅长而人类不擅长的事情。由于迭代如此普遍,Python 提供了多种语言特性以使其更加便捷。
Python 中的一种迭代形式是 while 语句。下面是一个简单的程序,它从五开始倒数,然后说“Blastoff!”:
n = 5
while n > 0:
print(n)
n = n - 1
print('Blastoff!')
你几乎可以把 while 语句读作英语。它的意思是:“当 n 大于 0 时,显示 n 的值,然后将 n 的值减 1。当到达 0 时,退出 while 语句并显示单词 Blastoff!。”
更正式地,这里是 while 语句的执行流程:
评估条件,得出
True或False。如果条件为假,则退出
while语句并继续执行下一条语句。如果条件为真,执行主体,然后返回步骤 1。
这种类型的流程被称为 循环,因为第三步会循环回到顶部。我们称每次执行循环体为一次 迭代。对于上述循环,我们会说“它有五次迭代”,这意味着循环体被执行了五次。
循环体应改变一个或多个变量的值,以便最终条件变为假,从而终止循环。我们把每次循环执行时都会改变并控制循环何时结束的变量称为 迭代变量(iteration variable) 。如果没有迭代变量,循环将无限重复,导致 infinite loop。
0.6.1.3. 无限循环¶
程序员们无尽的娱乐源泉在于观察到洗发水的说明“起泡,冲洗,重复”是一个无限循环,因为没有 迭代变量 告诉你循环需要执行多少次。
在 countdown 的情况下,我们可以证明循环会终止,因为我们知道 n 的值是有限的,并且可以看到 n 的值在每次循环中都变小,所以最终必然会达到 0。其他时候,循环显然是无限的,因为它根本没有迭代变量。
有时你直到执行到循环体的一半时才知道该结束循环。在这种情况下,你可以故意编写一个无限循环,然后使用 break 语句跳出循环。
之前的循环显然是 无限循环,因为 while 语句上的逻辑表达式仅仅是逻辑常量 True:
n = 10
while True:
print(n, end=' ')
n = n - 1
print('Done!')
如果您犯了错误并运行此代码,您将很快学会如何在系统上停止失控的 Python 进程,或找到您计算机上的电源关闭按钮。该程序将永远运行,直到电池耗尽,因为循环顶部的逻辑表达式由于该表达式是常量值 True 而始终为真。
虽然这是一个功能失调的无限循环,但我们仍然可以使用此模式来构建有用的循环,只要我们在达到退出条件时,仔细地在循环体中添加代码,使用 break 显式地退出循环。
例如,假设您想从用户那里接收输入,直到他们输入 done。您可以这样编写:
while True:
line = input('> ')
if line == 'done':
break
print(line)
print('Done!')
# Code: http://www.py4e.com/code3/copytildone1.py
循环条件为``True``,该条件始终为真,因此循环会重复执行,直到遇到 break 语句为止。
每次循环时,它都会提示用户输入一个尖括号。如果用户输入 done,则 break 语句退出循环。否则,程序会回显用户输入的内容,然后回到循环顶部。以下是示例运行:
> hello there
hello there
> finished
finished
> done
Done!
这种编写 while 循环的方式很常见,因为您可以在循环的任何位置检查条件(而不仅仅是在顶部),并且可以用肯定的方式表达停止条件(“当这种情况发生时停止”),而不是用否定的方式(“一直进行直到发生那种情况”)。
0.6.1.4. 完成迭代 continue¶
有时你处于循环的某次迭代中,并希望结束当前迭代并立即跳转到下一次迭代。在这种情况下,你可以使用 continue 语句来跳过当前迭代循环体的剩余部分,直接进入下一次迭代。
这是一个循环示例,它会复制输入直到用户键入"done",但将以井号字符开头的行视为不打印的行(有点像 Python 注释)。
while True:
line = input('> ')
if line[0] == '#':
continue
if line == 'done':
break
print(line)
print('Done!')
# Code: http://www.py4e.com/code3/copytildone2.py
以下是本新程序添加``continue``后的示例运行。
> hello there
hello there
> # don't print this
> print this!
print this!
> done
Done!
所有行都被打印,除了以井号(hash sign)开头的那一行,因为当执行 continue 时,它会结束当前迭代并跳转回 while 语句以开始下一次迭代,从而跳过 print 语句。
0.6.1.5. 使用 for 的确定循环¶
有时我们希望遍历一组事物,例如单词列表、文件中的行或数字列表。当我们要遍历一组事物时,可以使用 for 语句构造一个 确定 循环。我们将 while 语句称为 不定 循环,因为它只是循环直到某个条件变为 False,而 for 循环则是遍历已知的一组项目,因此会执行与集合中项目数量相等的迭代次数。
for 循环的语法与 while 循环相似,都包含一个 for 语句和一个循环体:
friends = ['Joseph', 'Glenn', 'Sally']
for friend in friends:
print('Happy New Year:', friend)
print('Done!')
在 Python 术语中,变量``friends``是一个包含三个字符串的列表:sup:`1 <https://www.py4e.com/html3/05-iterations#fn1>`__`,而``for``循环遍历该列表,对列表中的每个字符串执行一次循环体,从而产生以下输出:
Happy New Year: Joseph
Happy New Year: Glenn
Happy New Year: Sally
Done!
将这段 for 循环翻译成英文不如 while 那样直接,但如果你把朋友看作一个 集合,它就像这样:“运行 for 循环体中的语句一次,针对 集合 friends 中的每个朋友。”
查看 for 循环,for 和 in 是保留的 Python 关键字,而 friend 和 friends 是变量。
for friend in friends:
print('Happy New Year:', friend)
特别地,friend 是 for 循环的 iteration variable。变量 friend 在每次循环迭代中发生变化,并控制 for 循环何时完成。iteration variable 依次遍历存储在 friends 变量中的三个字符串。
0.6.1.6. 循环模式¶
通常我们使用 ``for`` 或 ``while`` 循环遍历一组项目或文件内容,并寻找其中某些特定值,例如扫描数据中的最大值或最小值。
这些循环通常通过以下方式构建:
在循环开始之前初始化一个或多个变量
对循环体中的每个项目执行一些计算,可能会改变循环体中的变量
查看循环完成后的结果变量
我们将使用数字列表来演示这些循环模式的原理与构建。
0.6.1.6.1. 计数与求和循环¶
例如,要计算列表中项目的数量,我们会编写如下 for 循环:
count = 0
for itervar in [3, 41, 12, 9, 74, 15]:
count = count + 1
print('Count: ', count)
我们在循环开始前将变量 count 设置为零,然后编写一个 for 循环来遍历数字列表。我们的 iteration 变量名为 itervar,尽管我们在循环中不使用 itervar,但它控制循环并导致循环体对列表中的每个值执行一次。
在循环体内,我们向列表中的每个值对应的当前值 `count` 加 1。当循环执行时,`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 为零,因为我们尚未看到任何数值;在循环过程中,total 是运行总计;在循环结束时,total 是列表中所有数值的总体总计。
随着循环执行,total 累加元素之和;以这种方式使用的变量有时被称为 累加器。
既不是计数循环也不是求和循环在实际中特别有用,因为存在内置函数 len() 和 sum(),它们分别计算列表中的项目数量以及列表中项目的总和。
0.6.1.6.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 是一个特殊的常数值,我们可以将其存储在变量中以标记该变量为“空”。
在循环开始之前,由于尚未看到任何值,我们迄今为止看到的最大值为 None。当循环执行时,如果 largest 是 None,则我们将看到的第一个值视为迄今为止的最大值。您可以在第一次迭代中看到,当 itervar 的值为 3 时,由于 largest 是 None,我们立即将 largest 设置为 3。
第一次迭代后,largest 不再等于 None,因此检查 itervar > 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 包含列表中的最小值。
同样地,正如在计数和求和中一样,内置函数 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 函数。
0.6.1.7. 调试¶
随着开始编写更大的程序,您可能会发现自己花费更多时间进行调试。更多的代码意味着出现错误的更多机会以及更多隐藏 bug 的地方。
减少调试时间的一种方法是“二分调试”。例如,如果程序中有 100 行代码,而你逐行检查,则需要 100 步。
相反,尝试将问题一分为二。查看程序的中部或其附近,寻找一个可以检查的中间值。添加一个 `print` 语句(或具有可验证效果的其他内容),然后运行程序。
如果中点检查不正确,那么问题一定出现在程序的前半部分。如果检查正确,那么问题就出现在后半部分。
每次执行此类检查时,您都将需要搜索的线路数量减半。经过六步(这远少于 100 步),您理论上将只剩下 1 或 2 行代码。
在实际应用中,并不总是清楚程序的“中间”在哪里,也不总是能够对其进行检查。逐行计数并找到精确的中点是没有意义的。相反,考虑程序中可能出现错误的位置以及容易放置检查的位置。然后选择一个你认为 bug 出现在检查之前或之后的概率大致相同的位置。
0.6.1.8. 术语表¶
- accumulator
在循环中用于累加或累积结果的变量。
- counter
在循环中用于计数某事发生次数的变量。我们将计数器初始化为零,然后每当我们要“计数”某事时递增计数器。
- decrement
减少变量值的更新。
- initialize
赋予将被更新的变量初始值的赋值。
- increment
增加变量值(通常加一)的更新。
- infinite loop
终止条件从未满足或根本没有终止条件的循环。
- iteration
使用调用自身的函数或循环重复执行一组语句。
0.6.1.9. 练习¶
练习 1:编写一个程序,反复读取数字,直到用户输入"done"。一旦输入"done",打印出数字的总和、计数和平均值。如果用户输入的不是数字,使用 ``try`` 和 ``except`` 检测其错误,打印错误消息并跳过下一个数字。
Enter a number: 4
Enter a number: 5
Enter a number: bad data
Invalid input
Enter a number: 7
Enter a number: done
16 3 5.333333333333333
练习 2:编写另一个程序,提示输入一个数字列表(如上),并在结束时打印出该列表的最大值和最小值,而不是平均值。
我们将在后面的章节中更详细地讨论列表。↩︎
如果您发现本书中有错误,欢迎使用 Github 向我发送修正。
