4.3 循环结构
4.3.1 for循环与while循环
Python主要有for循环和while循环两种形式的循环结构,多个循环可以嵌套使用,并且还经常和选择结构嵌套使用来实现复杂的业务逻辑。while循环一般用于循环次数难以提前确定的情况,当然也可以用于循环次数确定的情况;for循环一般用于循环次数可以提前确定的情况,尤其适用于枚举或遍历序列或迭代对象中元素的场合。对于带有else子句的循环结构,如果循环因为条件表达式不成立或序列遍历结束而自然结束时则执行else结构中的语句,如果循环是因为执行了break语句而导致循环提前结束则不会执行else中的语句。两种循环结构的完整语法形式分别为
while条件表达式: 循环体 [else: else子句代码块]
和
for取值in序列或迭代对象: 循环体 [else: else子句代码块]
其中,方括号内的else子句可以没有,也可以有。下面的代码使用循环结构遍历并输出列表中的所有元素。
a_list=['a', 'b', 'mpilgrim', 'z', 'example'] for i, v in enumerate(a_list): print(’列表的第’, i+1, ’个元素是:', v)
下面的代码用来输出1~100之间能被7整除但不能同时被5整除的所有整数。
for i in range(1,101): if i% 7==0 and i% 5! =0: print(i)
下面的代码使用嵌套的循环结构打印九九乘法表。
for i in range(1,10): for j in range(1, i+1): print('{0}*{1}={2}'.format(i, j, i*j), end='') print() #打印空行
下面的代码演示了带有else子句的循环结构,该代码用来计算1+2+3+…+99+100的结果。
s=0 for i in range(1,101): #不包括101 s+=i else: print(s)
下面的代码使用while循环实现了同样的功能:
s=i=0 while i<=100: s+=i i+=1 else: print(s)
当然,上面的两段代码只是为了演示循环结构的用法,其中的else子句实际上并没有必要,循环结束后直接输出结果就可以了。另外,如果只是要计算1+2+3+…+99+100的值的话,直接用内置函数sum()和range()就可以了。
>>>sum(range(1,101)) 5050
4.3.2 break与continue语句
break与continue语句在while循环和for循环中都可以使用,并且一般常与选择结构或异常处理结构结合使用。一旦break语句被执行,将使得break语句所属层次的循环提前结束;continue语句的作用是提前结束本次循环,忽略continue之后的所有语句,提前进入下一次循环。
下面的代码用来计算小于100的最大素数,内循环用来测试特定的整数n是否为素数,如果其中的break语句得到执行则说明n不是素数,并且由于循环提前结束而不会执行后面的else子句。如果某个整数n为素数,则内循环中的break语句不会执行,内循环自然结束后执行后面else子句中的语句,输出素数n之后执行break语句跳出外循环。
for n in range(100,1, -1): if n% 2==0: continue for i in range(3, int(n**0.5)+1,2): if n% i==0: #结束内循环 break else: print(n) #结束外循环 break
需要注意的是,过多的break和continue语句会降低程序的可读性。因此,除非break或continue语句可以让代码更简单或更清晰,否则不要轻易使用。
4.3.3 循环代码优化技巧
实际开发中,正确实现了预定功能之后,一般还需要再优化一下代码以追求更高的执行效率。如果能从算法层面上进行优化,那毫无疑问会带来效率的大幅度提升。例如,判断一个大整数n是否为素数,如果根据素数定义去判断的话应该逐个测试[2, n-1]区间上的数是否能够整除n,而实际上只需要判断从2到n的平方根这个小范围就可以了,再进一步说,实际上只需要判断2以及3到n的平方根之间所有奇数这个更小的范围。对于大整数n来说,循环次数和余数运算的次数减少是非常可观的,n越大算法效率的提高越显著。
在编写循环语句时,应尽量减少循环内部不必要或无关的计算,与循环变量无关的代码应该尽可能地提取到循环之外。尤其是多重循环嵌套的情况,一定要尽量减少内层循环中不必要的计算,尽最大可能地把计算向外提。例如下面的代码,第二段明显比第一段的运行效率要高。
digits=(1,2,3,4) for i in range(1000): result=[] for i in digits: for j in digits: for k in digits: result.append(i*100+j*10+k) for i in range(1000): result=[] for i in digits: i=i*100 for j in digits: j=j*10 for k in digits: result.append(i+j+k)
另外,在循环中应尽量引用局部变量,局部变量的查询和访问速度比全局变量略快。同样的道理,在使用模块中的方法时,可以通过将其转换为局部变量来提高运行速度。例如下面的代码,第二段代码的速度就比第一段代码略快。当然,也可以使用from math import sin as loc_sin来代替其中的写法。
import math for i in range(10000000): math.sin(i) loc_sin=math.sin for i in range(10000000): loc_sin(i)
代码优化涉及的内容非常广泛,除了在算法层面的优化之外,编码过程本身对程序员的功底要求也非常高。除了上面介绍的循环代码优化,本书其他章节中也会涉及一些优化的内容。例如,如果经常需要测试一个序列是否包含一个元素就应该尽量使用字典或集合而不使用列表,把多个字符串连接成一个字符串时尽量使用join()方法而不要使用运算符+,对列表进行元素的插入和删除操作时应尽量从列表尾部进行,等等。实际开发中需要注意的是,首先要把代码写对,保证完全符合功能要求,然后再进行必要的优化来提高性能。过早地追求性能优化有时候可能会带来灾难而浪费大量精力。