3.3 循环语句
另外一种控制语句是循环语句。循环顾名思义就是重复地执行一些语句块,典型的例子就是老师会修改学生们同样的试卷。循环语句又可以分为while语句和for语句,此外还会衍生出一些跳转语句,如break语句和continue语句等。
3.3.1 while语句
while循环是常用的循环方式,它的结构如下:
其中,else语句是可选的。while语句的执行流程如图3-4所示。
图3-4 while语句的执行流程
接下来举例说明如何使用while语句。在小学趣味数学中,会常常问到从1到100的整数相加的结果是什么。这对于while语句来说就是相对简单的,下面通过代码清单3-9进行计算。
代码清单3-9:使用while语句计算从1到100的整数相加的和
需要注意的是,这里的变量n是一个从1到100变化的整数。
其意思是每次循环,n就加1,而循环的条件是n <= 100,这样就能控制整数的范围是[1, 100]。而变量total是计算总和,从而得到最后的结果。执行代码清单3-9就可以得到下面的输出结果:
如果while语句的代码只有一行,则可以像下面一样编写代码:
此时while语句的代码块就只有n += 1这一行,只是这样的方式可读性并不高,所以笔者也不推荐这种方式,即使while语句的代码只有一行,也应该换行编写,因为这样可以提高代码的可读性,如下所示:
3.3.2 使用循环的注意事项
使用循环的注意事项主要包括两点:一是避免死循环(或称为无限循环),也就是要注意退出循环的条件;二是准确把握边界。下面分别进行探讨。
所谓死循环是指循环条件一直成立,永远都不退出循环,如下代码就是死循环:
如果运行上述代码,就会一直执行n += 1这行代码,这将消耗计算机很多的资源。
在一般情况下,应该避免死循环,或者有机制退出循环,关于退出循环可以考虑使用后面介绍的break语句,本节先讨论如何避免死循环。避免死循环的关键点在于循环条件的编写,如这里的n > 0就已经决定了它是一个死循环,如果将循环条件修改为n <= 10,那么当n=11时就可以退出循环,代码如下:
说明 死循环的应用
死循环并非一无是处,在很多情况下死循环还是可用的,如一个服务器的服务永远在运行,一般来说它会让任务处于休眠状态,从而降低计算机资源的损耗。当有外部请求到达时,它就会进入工作状态来完成用户的请求。当它完成用户的请求之后,又会进入休眠状态,继续等待外部请求的到来。这就是一种典型的死循环的应用。当然,在编写代码时应该考虑死循环的合理性,即使存在死循环,也要考虑如何退出死循环,毕竟原本处于死循环服务的服务器也是允许正常关闭的。
使用循环的另一个问题是边界问题,任意字符串都是存在长度的,不慎搞错边界会触发异常,如代码清单3-10所示。
代码清单3-10:越界问题
运行上述代码得到的输出结果如下:
从异常中可以看到“string index out of range”这样的提示,这是因为当index == len(s)时,就会在执行s[index]时越界。这里index的界限应该是整数区间[0, len(s) -1),出现执行异常是因为循环条件为index <= len(s),使index == len(s)成为可能,所以只需要将循环条件修改为index < len(s)就可以。由此可以看出,正确界定循环的边界是十分重要的。
3.3.3 for语句
for循环比while循环更加常用,因为其功能更加强大。for语句的结构如下:
其中,else语句是可选的。for语句的执行流程如图3-5所示。
图3-5 for语句的执行流程
在代码清单3-9中使用while语句实现了从1加到100,使用for语句也可以完成,如代码清单3-11所示。
代码清单3-11:使用for语句计算从1到100的和
这里的n就是序列rang(1, 101)中的变量,for语句将遍历这个序列,使total得到从1到100的和。除了数字,for语句还可以遍历Python的数据结构(如列表),如代码清单3-12所示。
代码清单3-12:使用for语句遍历列表
3.3.4 range函数
在for循环中,range是一个使用比较广泛的函数,所以本节主要介绍range函数的使用。在range函数中存在以下3个参数。
• start:初始值,默认值为0。
• stop:最大值,在Python中区间采用左闭右开的规则,所以范围不包括这个值。
• step:步长,在默认情况下每次递增1,如果将step设置为2,则每次递增2。
下面通过举例来说明,如代码清单3-13所示。
代码清单3-13:使用range函数
代码①处的range函数只设置了一个整数11,也就是将stop设置为11,而默认start为0,step为1,这个时候它将遍历区间[0, 11)中的11个整数。代码②处的range函数设置了区间[6, 11),step为1,所以遍历区间[6, 11)中的5个整数。代码③处的range函数将start设置为2,stop设置为11,step设置为2,由于step为2,因此每次递增2,它将遍历区间[2, 11)中的偶数。
对于一些数据类型,有时我们希望按下标进行遍历而不是按元素进行遍历,此时也可以使用range函数,如代码清单3-14就是一个以下标遍历字符串字符的例子。
代码清单3-14:使用range函数遍历字符串
需要注意的是,在代码中使用len函数得到字符串的长度就不容易越界,并且可读性更好。
3.3.5 跳转关键字break、continue和pass
有时循环并不是为了遍历所有的元素,而只是为了在某个集合中找到对应的值,一旦找到对应的值就即刻返回,不再继续访问。例如,如果需要在字符串“人生苦短,我用Python”中确定“P”的下标,当找到“P”之后,就不再遍历后续的字符串“ython”,这就需要存在退出循环的机制。在Python中,完成循环中断功能的是break关键字。下面通过代码清单3-15来实现这个功能。
代码清单3-15:使用break关键字退出循环
加粗的代码在找到“P”的下标的情况下才执行break关键字。当遇到break关键字时,程序就会退出当前循环,并且不再执行循环的else分支。所以,运行上述代码得到的输出结果如下:
此外,在Python中还存在一个关键字——continue,它的作用是跳过本次循环,然后直接进入下一轮的循环。在美国前总统肯尼迪的演讲中,有这样一句话——Ask not what your country can do for you — ask what you can do for your country(不要问你的国家能为你做什么,而是要问你能为你的国家做什么)。下面统计这句话中有多少个元音字母(a、e、i、o和u为元音字母),如代码清单3-16所示。
代码清单3-16:统计元音字母
加粗的continue表示跳过当前循环中的字符和其后续的语句,直接进入下一轮循环。运行上述代码得到的输出结果如下:
此外,在Python中还存在关键字pass,pass语句的用途在于空实现。有时可能会因为某种原因不能完成代码,如需求不够清晰,此时可以使用pass语句来暂时标识,说明这段代码需要在将来完成。例如,遍历字符串“人生苦短,我用Python”,当遇到英文字母时就打印出来,当遇到其他字符时可能还没有想好处理方法,就可以写成代码清单3-17的样子。
代码清单3-17:pass语句的使用
最后加粗的pass代表目前还不知道应该如何处理,等待后续讨论清楚之后再来完成对应的代码,这样就可以保证逻辑分支的完整。
3.3.6 循环的嵌套
和条件语句一样,循环语句也可以嵌套,笔者建议嵌套不要超过3层,因为嵌套的层次过多会使代码的可读性急剧下降。在循环的嵌套的程序设计中有两个经典的例子,一个是九九乘法表,另一个是寻找质数,下面分别进行讲解。
九九乘法表是学习数学的基础。九九乘法表如下:
假设i为纵向控制变量,j为横向控制变量,那么在每行i>=j的规则下,每个i都需要一次换行,依照这些规律来实现Python版的九九乘法表,如代码清单3-18所示。
代码清单3-18:九九乘法表
这样就完成了九九乘法表的输出。
下面讨论质数的问题。所谓质数(或素数)就是大于1且只能被1和其本身整除的整数。例如,13只能被1和13整除,所以它就是质数;18能被3整除,而3不是1和数字本身,所以18就不是质数。也就是说,一个非质数N需要满足以下条件:
其中,a和b被称为因子,根据非质数的这个特征,下面求区间[1, 100]中的质数,如代码清单3-19所示。
代码清单3-19:求区间[1, 100]中的质数
上述代码的关键是加粗的嵌套循环,它会遍历整数区间[2, i-1]寻找因子,如果可以找到因子则不是质数,如果找不到就使用else语句输出质数。
质数问题之所以成为经典例子并非那么简单,假设要寻找区间[1,29999]中的质数,那么通过这样的循环寻找因子的计算量就会比较大,所以需要使用简化的方法。
说明 循环嵌套的弊端
寻找区间[1, 29999]中的质数。以29999为例,假设它不是质数,那么必然存在a和b满足:
这里假设a>=b,那么必然存在b <= math.sqrt(29999),这样就不需要遍历区间[2, 29998],而只需要遍历区间[2, int(math.sqrt(29999))]寻找b,如果可以找到则说明它不是质数,如果找不到则说明它是质数。由此可见,通过一定的规律减少循环的次数,从而减少内层循环的计算次数,以提高性能,这是在做循环嵌套时应该注意的事情。
由此可知,只需要将数字开平方,就可以通过缩小区间来寻找因子,下面通过代码清单3-20来寻找区间[1, 29999]中的质数。
代码清单3-20:寻找区间[0, 29999]中的质数
需要注意加粗的代码,这里先开平方,再缩小循环区间,从而使内层循环计算的次数大大降低,这样就能提高程序的性能。
注意 循环嵌套的注意点
循环嵌套意味着大量计算,如果外层循环是1000,内层循环是100,那么需要计算1000×100 = 100 000次。因此,在使用循环嵌套时,一定要评估循环的次数,如果次数比较多,就应该考虑是否存在减少嵌套的方法,以及是否能够根据业务特点找到有效减少循环次数的方法。这里求质数的方法就是一个典型的案例,通过开平方减少了循环的次数,大大地提高了程序的性能。
同样,和if语句一样,循环嵌套不应该超过3层,超过3层的嵌套会影响程序的可读性,增加程序的复杂度。