《编写高质量Python代码的59个有效方法》——第12条:不要在for和while循环后面写else块

第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块中。

时间: 2024-11-02 03:46:14

《编写高质量Python代码的59个有效方法》——第12条:不要在for和while循环后面写else块的相关文章

《编写高质量Python代码的59个有效方法》——导读

目 录 前 言 致 谢第1章 用Pythonic方式来思考 第1条:确认自己所用的Python版本 第2条:遵循PEP 8风格指南 第3条:了解bytes.str与unicode的区别第4条:用辅助函数来取代复杂的表达式第5条:了解切割序列的办法第6条:在单次切片操作内,不要同时指定start.end和stride第7条:用列表推导来取代map和f?ilter 第8条:不要使用含有两个以上表达式的列表推导第9条:用生成器表达式来改写数据量较大的列表推导第10条:尽量用enumerate取代ran

《编写高质量Python代码的59个有效方法》——第3条:了解bytes、str与unicode的区别

第3条:了解bytes.str与unicode的区别 Python 3有两种表示字符序列的类型:bytes和str.前者的实例包含原始的8位值:后者的实例包含Unicode字符. Python 2也有两种表示字符序列的类型,分别叫做str和unicode.与Python 3不同的是,str的实例包含原始的8位值:而unicode的实例,则包含Unicode字符. 把Unicode字符表示为二进制数据(也就是原始8位值)有许多种办法.最常见的编码方式就是UTF-8.但是大家要记住,Python 3

《编写高质量Python代码的59个有效方法》——第2条:遵循PEP 8风格指南

第2条:遵循PEP 8风格指南 <Python Enhancement Proposal #8>(8号Python增强提案)又叫PEP 8,它是针对Python代码格式而编订的风格指南.尽管可以在保证语法正确的前提下随意编写Python代码,但是,采用一致的风格来书写可以令代码更加易懂.更加易读.采用和其他Python程序员相同的风格来写代码,也可以使项目更利于多人协作.即便代码只会由你自己阅读,遵循这套风格也依然可以令后续的修改变得容易一些. PEP 8列出了许多细节,以描述如何撰写清晰的P

《编写高质量Python代码的59个有效方法》——第8条:不要使用含有两个以上表达式的列表推导

第8条:不要使用含有两个以上表达式的列表推导除了基本的用法(参见本书第7条)之外,列表推导也支持多重循环.例如,要把矩阵(也就是包含列表的列表,即二维列表)简化成一维列表,使原来的每个单元格都成为新列表中的普通元素.这个功能采用包含两个for表达式的列表推导即可实现,这些for表达式会按照从左至右的顺序来评估. 上面这个例子简单易懂,这就是多重循环的合理用法.还有一种包含多重循环的合理用法,那就是根据输入列表来创建有两层深度的新列表.例如,我们要对二维矩阵中的每个单元格取平方,然后用这些平方值构

《编写高质量Python代码的59个有效方法》——第15条:了解如何在闭包里使用外围作用域中的变量

第15条:了解如何在闭包里使用外围作用域中的变量 假如有一份列表,其中的元素都是数字,现在要对其排序,但排序时,要把出现在某个群组内的数字,放在群组外的那些数字之前.这种用法在绘制用户界面时候可能会遇到,我们可以用这个办法把重要的消息或意外的事件优先显示在其他内容前面. 实现该功能的一种常见做法,是在调用列表的sort方法时,把辅助函数传给key参数.这个辅助函数的返回值,将会用来确定列表中各元素的顺序.辅助函数可以判断受测元素是否处在重要群组中,并据此返回相应的排序关键字(sort key).

《编写高质量Python代码的59个有效方法》——第13条:合理利用try/except/else/f?inally结构中的每个代码块

第13条:合理利用try/except/else/f?inally结构中的每个代码块Python程序的异常处理可能要考虑四种不同的时机.这些时机可以用try.except.else和f?inally块来表述.复合语句中的每个块都有特定的用途,它们可以构成很多种有用的组合方式(参见本书第51条).1.?f?inally块如果既要将异常向上传播,又要在异常发生时执行清理工作,那就可以使用try/f?inally结构.这种结构有一项常见的用途,就是确保程序能够可靠地关闭文件句柄(还有另外一种写法,参见

《编写高质量Python代码的59个有效方法》——第17条:在参数上面迭代时,要多加小心

第17条:在参数上面迭代时,要多加小心如果函数接受的参数是个对象列表,那么很有可能要在这个列表上面多次迭代.例如,要分析来美国Texas旅游的人数.假设数据集是由每个城市的游客数量构成的(单位是每年百万人).现在要统计来每个城市旅游的人数,占总游客数的百分比.为此,需要编写标准化函数(normalization function).它会把所有的输入值加总,以求出每年的游客总数.然后,用每个城市的游客数除以总数,以求出该城市所占的比例. 把各城市的游客数量放在一份列表里,传给该函数,可以得到正确结

《编写高质量Python代码的59个有效方法》——第7条:用列表推导来取代map和f?ilter

第7条:用列表推导来取代map和f?ilterPython提供了一种精练的写法,可以根据一份列表来制作另外一份.这种表达式称为list comprehension(列表推导).例如,要用列表中每个元素的平方值构建另一份列表.如果采用列表推导来实现,那就同时指定制作新列表时所要迭代的输入序列,以及计算新列表中每个元素的值时所用的表达式. 除非是调用只有一个参数的函数,否则,对于简单的情况来说,列表推导要比内置的map函数更清晰.如果使用map,那就要创建lambda函数,以便计算新列表中各个元素的

《编写高质量Python代码的59个有效方法》——第20条:用None和文档字符串来描述具有动态默认值的参数

第20条:用None和文档字符串来描述具有动态默认值的参数有时我们想采用一种非静态的类型,来做关键字参数的默认值.例如,在打印日志消息的时候,要把相关事件的记录时间也标注在这条消息中.默认情况下,消息里面所包含的时间,应该是调用log函数那一刻的时间.如果我们以为参数的默认值会在每次执行函数时得到评估,那可能就会写出下面这种代码. 两条消息的时间戳(timestamp)是一样的,这是因为datetime.now只执行了一次,也就是它只在函数定义的时候执行了一次.参数的默认值,会在每个模块加载进来