第12条:不要在for和while循环后面写else块
Python提供了一种很多编程语言都不支持的功能,那就是可以在循环内部的语句块后面直接编写else块。
奇怪的是,这种else块会在整个循环执行完之后立刻运行。既然如此,那它为什么叫做else呢?为什么不叫and?在if/else语句中,else的意思是:如果不执行前面那个if块,那就执行else块。在try/except语句中,except的定义也类似:如果前面那个try块没有成功执行,那就执行except块。
同理,try/except/else也是如此(参见本书第13条),该结构的else的含义是:如果前面的try块没有失败,那就执行else块。try/f?inally同样非常直观,这里的f?inally的意思是:执行过前面的try块之后,总是执行f?inally块。
明白了else、except和f?inally的含义之后,刚接触Python的程序员可能会把for/else结构中的else块理解为:如果循环没有正常执行完,那就执行else块。实际上刚好相反——在循环里用break语句提前跳出,会导致程序不执行else块。
还有一个奇怪的地方:如果for循环要遍历的序列是空的,那么就会立刻执行
else块。
初始循环条件为false的while循环,如果后面跟着else块,那它也会立刻执行。
知道了循环后面的else块所表现出的行为之后,我们会发现:在搜索某个事物的时候,这种写法是有意义的。例如,要判断两个数是否互质(也就是判断两者除了1之外,是否没有其他的公约数),可以把有可能成为公约数的每个值都遍历一轮,逐个判断两数是否能以该值为公约数。尝试完每一种可能的值之后,循环就结束了。如果两个数确实互质,那么在执行循环的过程中,程序就不会因break语句而跳出,于是,执行完循环后,程序会紧接着执行else块。
实际上,我们不会这样写代码,而是会用辅助函数来完成计算。这样的辅助函数,有两种常见的写法。
第一种写法是,只要发现受测参数符合自己想要搜寻的条件,就尽早返回。如果整个循环都完整地执行了一遍,那就说明受测参数不符合条件,于是返回默认值。
第二种写法是,用变量来记录受测参数是否符合自己想要搜寻的条件。一旦符合,就用break跳出循环。
对于不熟悉for/else的人来说,这两种写法都要比早前那种写法清晰很多。for/else结构中的else块虽然也能够实现相应的功能,但是将来回顾这段程序的时候,却会令阅读代码的人(包括你自己)感到相当费解。像循环这种简单的语言结构,在Python程序中应该写得非常直白才对。所以,我们完全不应该在循环后面使用else块。
要点
Python有种特殊语法,可在for及while循环的内部语句块之后紧跟一个
else块。
只有当整个循环主体都没遇到break语句时,循环后面的else块才会执行。
不要在循环后面使用else块,因为这种写法既不直观,又容易引人误解。
第13条:合理利用try/except/else/f?inally结构中的每个代码块
Python程序的异常处理可能要考虑四种不同的时机。这些时机可以用try、except、else和f?inally块来表述。复合语句中的每个块都有特定的用途,它们可以构成很多种有用的组合方式(参见本书第51条)。
1.?f?inally块
如果既要将异常向上传播,又要在异常发生时执行清理工作,那就可以使用try/f?inally结构。这种结构有一项常见的用途,就是确保程序能够可靠地关闭文件句柄(还有另外一种写法,参见本书第43条)。
在上面这段代码中,read方法所抛出的异常会向上传播给调用方,而f?inally块中的handle.close方法则一定能够执行。open方法必须放在try块外面,因为如果打开文件时发生异常(例如,由于找不到该文件而抛出IOError),那么程序应该跳过f?inally块。
2.else块
try/except/else结构可以清晰地描述出哪些异常会由自己的代码来处理、哪些异常会传播到上一级。如果try块没有发生异常,那么就执行else块。有了这种else块,我们可以尽量缩减try块内的代码量,使其更加易读。例如,要从字符串中加载JSON字典数据,然后返回字典里某个键所对应的值。
如果数据不是有效的JSON格式,那么用json.loads解码时,会产生ValueError。这个异常会由except块来捕获并处理。如果能够解码,那么else块里的查找语句就会执行,它会根据键来查出相关的值。查询时若有异常,则该异常会向上传播,因为查询语句并不在刚才那个try块的范围内。这种else子句,会把try/except后面的内容和except块本身区分开,使异常的传播行为变得更加清晰。
3.混合使用
如果要在复合语句中把上面几种机制都用到,那就编写完整的try/except/else/f?inally结构。例如,要从文件中读取某项事务的描述信息,处理该事务,然后就地更新该文件。为了实现此功能,我们可以用try块来读取文件并处理其内容,用except块来应对try块中可能发生的相关异常,用else块实时地更新文件并把更新中可能出现的异常回报给上级代码,然后用f?inally块来清理文件句柄。
这种写法很有用,因为这四块代码互相配合得非常到位。例如,即使else块在写入result数据时发生异常,f?inally块中关闭文件句柄的那行代码,也依然能执行。
要点
无论try块是否发生异常,都可利用try/f?inally复合语句中的f?inally块来执行清理工作。
else块可以用来缩减try块中的代码量,并把没有发生异常时所要执行的语句与try/except代码块隔开。
顺利运行try块后,若想使某些操作能在f?inally块的清理代码之前执行,则可将这些操作写到else块中。