文章目录
Python 异常处理
python提供了两个非常重要的功能来处理python程序在运行中出现的异常和错误。你可以使用该功能来调试python程序。
- 异常处理
- 断言(Assertions)
Python异常机制使用细则,正确使用Python异常处理机制
前面介绍了使用异常处理的优势、便捷之处,本节将进一步从程序性能优化、结构优化的角度给出异常处理的一般规则。
成功的异常处理应该实现如下 4 个目标:
- 使程序代码混乱最小化。
- 捕获并保留诊断信息。
- 通知合适的人员。
- 采用合适的方式结束异常活动。
下面介绍达到这些效果的基本准则。
不要过度使用异常
不可否认,Python 的异常机制确实方便,但滥用异常机制也会带来一些负面影响。过度使用异常主要表现在两个方面:
- 把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是以简单地引发异常来代苦所有的错误处理。
- 使用异常处理来代替流程控制。
熟悉了异常使用方法后,程序员可能不再愿意编写烦琐的错误处理代码,而是简单地引发异常。实际上这样做是不对的,对于完全己知的错误和普通的错误,应该编写处理这种错误的代码,增加程序的健壮性。只有对于外部的、不能确定和预知的运行时错误才使用异常。
对比前面五子棋游戏中,处理用户输入坐标点己有棋子的两种方式。如果用户试图下棋的坐标点己有棋子:
#如果要下棋的点不为空
if board[int(y_str) - 1) [int(x_str) - 1] !="╋" :
inputStr = input ("您输入的坐标点己有棋子了,请重新输入\n")
continue
上面这种处理方式检测到用户试图下棋的坐标点己经有棋子,立即打印一条提示语句,并重新开始下一次循环。这种处理方式简洁明了、逻辑清晰,程序的运行效率也很好程序进入 if 块后,即结束了本次循环。
如果将上面的处理机制改为如下方式:
#如果要下棋的点不为空
if board[int(y_str) - 1) [int(x_str) - 1) != "╋":
#引发默认的RuntimeError 异常
raise
上面这种处理方式没有提供有效的错误处理代码,当程序检测到用户试图下棋的坐标点己经有棋子时,并没有提供相应的处理,而是简单地引发一个异常。这种处理方式虽然简单,但 Python 解释器接收到这个异常后,还需要进入相应的 except 块来捕获该异常,所以运行效率要差一些。而且用户下棋重复这个错误完全是可预料的,所以程序完全可以针对该错误提供相应的处理,而不是引发异常。
必须指出,异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,因此绝不要使用异常处理来代替正常的业务逻辑判断。
另外,异常机制的效率比正常的流程控制效率差,所以不要使用异常处理来代替正常的程序流程控制。例如,对于如下代码:
#定义一个字符串列表
my_list =["Hello", "Python", "Spring"]
#使用异常处理来遍历arr数组的每个元素
try:
i = 0
while True:
print (my_list [i])
i += 1
except:
pass
运行上面程序确实可以实现遍历 my_list 列表的功能,但这种写法可读性较差,而且运行效率也不高。程序完全有能力避免产生 indexError 异常,程序“故意”制造这种异常,然后使用 except 块去捕获该异常,这是不应该的。将程序改为如下形式肯定要好得多:
i = 0
while i < len(my_list):
print(my_list[i])
i += 1
注意,异常只应该用于处理非正常的情况,不要使用异常处理来代替正常的流程控制。对于一些完全可预知,而且处理方式清楚的错误,程序应该提供相应的错误处理代码,而不是将其笼统地称为异常。
不要使用过于庞大的 try 块
很多初学异常机制的读者喜欢在 try 块里放置大量的代码,这看上去很“简单”,但这种“简单”只是一种假象,只是在编写程序时看上去比较简单。但因为 try 块里的代码过于庞大,业务过于复杂,就会造成 try 块中出现异常的可能性大大增加,从而导致分析异常原因的难度也大大增加。
而且当时块过于庞大时,就难免在 try 块后紧跟大量的 except 块才可以针对不同的异常提供不同的处理逻辑。在同一个 try 块后紧跟大量的 except 块则需要分析它们之间的逻辑关系,反而增加了编程复杂度。
正确的做法是,把大块的 try 块分割成多个可能出现异常的程序段落,并把它们放在单独的 try 块中,从而分别捕获并处理异常。
不要忽略捕获到的异常
不要忽略异常!既然己捕获到异常,那么 except 块理应做些有用的事情,及处理并修复异常。except 块整个为空,或者仅仅打印简单的异常信息都是不妥的!
except 块为空就是假装不知道甚至瞒天过海,这是最可怕的事情,程序出了错误,所有人都看不到任何异常,但整个应用可能已经彻底坏了。仅在 except 块里打印异常传播信息稍微好一点,但仅仅比空白多了几行异常信息。通常建议对异常采取适当措施,比如:
- 处理异常。对异常进行合适的修复,然后绕过异常发生的地方继续运行;或者用别的数据进行计算,以代替期望的方法返回值;或者提示用户重新操作……总之,程序应该尽量修复异常,使程序能恢复运行。
- 重新引发新异常。把在当前运行环境下能做的事情尽量做完,然后进行异常转译,把异常包装成当前层的异常,重新传给上层调用者。
- 在合适的层处理异常。如果当前层不清楚如何处理异常,就不要在当前层使用 except 语句来捕获该异常,让上层调用者来负责处理该异常。
python标准异常
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError Python | 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
什么是异常?
异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。
一般情况下,在Python无法正常处理程序时就会发生一个异常。
异常是Python对象,表示一个错误。
当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。
异常处理
捕捉异常可以使用try/except语句。
try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。
如果你不想在异常发生时结束你的程序,只需在try里捕获它。
语法:
以下为简单的try…except…else的语法:
try:
<语句> #运行别的代码
except <名字>:
<语句> #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句> #如果引发了'name'异常,获得附加的数据
else:
<语句> #如果没有异常发生
try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。
- 如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)。
- 如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印默认的出错信息)。
- 如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句。
实例
下面是简单的例子,它打开一个文件,在该文件中的内容写入内容,且并未发生异常:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
except IOError:
print "Error: 没有找到文件或读取文件失败"
else:
print "内容写入文件成功"
fh.close()
以上程序输出结果:
$ python test.py
内容写入文件成功
$ cat testfile # 查看写入的内容
这是一个测试文件,用于测试异常!!
实例
下面是简单的例子,它打开一个文件,在该文件中的内容写入内容,但文件没有写入权限,发生了异常:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
except IOError:
print "Error: 没有找到文件或读取文件失败"
else:
print "内容写入文件成功"
fh.close()
在执行代码前为了测试方便,我们可以先去掉 testfile 文件的写权限,命令如下:
chmod -w testfile
再执行以上代码:
$ python test.py
Error: 没有找到文件或读取文件失败
使用except而不带任何异常类型
你可以不带任何异常类型使用except,如下实例:
try:
正常的操作
......................
except:
发生异常,执行这块代码
......................
else:
如果没有异常执行这块代码
以上方式try-except语句捕获所有发生的异常。但这不是一个很好的方式,我们不能通过该程序识别出具体的异常信息。因为它捕获所有的异常。
使用except而带多种异常类型
你也可以使用相同的except语句来处理多个异常信息,如下所示:
try:
正常的操作
......................
except(Exception1[, Exception2[,...ExceptionN]]]):
发生以上多个异常中的一个,执行这块代码
......................
else:
如果没有异常执行这块代码
try-finally 语句
try-finally 语句无论是否发生异常都将执行最后的代码。
try:
<语句>
finally:
<语句> #退出try时总会执行
raise
实例
#!/usr/bin/python
# -*- coding: UTF-8 -*-
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
finally:
print "Error: 没有找到文件或读取文件失败"
如果打开的文件没有可写权限,输出如下所示:
$ python test.py
Error: 没有找到文件或读取文件失败
同样的例子也可以写成如下方式:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
try:
fh = open("testfile", "w")
try:
fh.write("这是一个测试文件,用于测试异常!!")
finally:
print "关闭文件"
fh.close()
except IOError:
print "Error: 没有找到文件或读取文件失败"
当在try块中抛出一个异常,立即执行finally块代码。
finally块中的所有语句执行后,异常被再次触发,并执行except块代码。
参数的内容不同于异常。
异常的参数
一个异常可以带上参数,可作为输出的异常信息参数。
你可以通过except语句来捕获异常的参数,如下所示:
try:
正常的操作
......................
except ExceptionType, Argument:
你可以在这输出 Argument 的值...
变量接收的异常值通常包含在异常的语句中。在元组的表单中变量可以接收一个或者多个值。
元组通常包含错误字符串,错误数字,错误位置。
实例
以下为单个异常的实例:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 定义函数
def temp_convert(var):
try:
return int(var)
except ValueError, Argument:
print "参数没有包含数字\n", Argument
# 调用函数
temp_convert("xyz");
以上程序执行结果如下:
$ python test.py
参数没有包含数字
invalid literal for int() with base 10: 'xyz'
触发异常
我们可以使用raise语句自己触发异常
raise语法格式如下:
raise [Exception [, args [, traceback]]]
语句中 Exception 是异常的类型(例如,NameError)参数标准异常中任一种,args 是自已提供的异常参数。
最后一个参数是可选的(在实践中很少使用),如果存在,是跟踪异常对象。
实例
一个异常可以是一个字符串,类或对象。 Python的内核提供的异常,大多数都是实例化的类,这是一个类的实例的参数。
定义一个异常非常简单,如下所示:
def functionName( level ):
if level < 1:
raise Exception("Invalid level!", level)
# 触发异常后,后面的代码就不会再执行
注意
:为了能够捕获异常,"except"语句必须有用相同的异常来抛出类对象或者字符串。
例如我们捕获以上异常,"except"语句如下所示:
try:
正常逻辑
except Exception,err:
触发自定义异常
else:
其余代码
实例
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 定义函数
def mye( level ):
if level < 1:
raise Exception,"Invalid level!"
# 触发异常后,后面的代码就不会再执行
try:
mye(0) # 触发异常
except Exception,err:
print 1,err
else:
print 2
执行以上代码,输出结果为:
$ python test.py
1 Invalid level!
用户自定义异常
通过创建一个新的异常类,程序可以命名它们自己的异常。异常应该是典型的继承自Exception类,通过直接或间接的方式。
以下为与RuntimeError相关的实例,实例中创建了一个类,基类为RuntimeError,用于在异常触发时输出更多的信息。
在try语句块中,用户自定义的异常后执行except块语句,变量 e 是用于创建Networkerror类的实例。
class Networkerror(RuntimeError):
def __init__(self, arg):
self.args = arg
在你定义以上类后,你可以触发该异常,如下所示:
try:
raise Networkerror("Bad hostname")
except Networkerror,e:
print e.args
Python try except else(异常处理)用法详解
Python 的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。当程序运行出现意外情况时,系统会自动生成一个 Error 对象来通知程序,从而实现将“业务实现代码”和“错误处理代码”分离,提供更好的可读性。
使用try…except捕获异常
前面章节讲过,希望有一个非常强大的“if 块”,可以表示所有的错误情况,让程序一次处理所有的错误,也就是希望将错误集中处理。
出于这种考虑,此处试图把“错误处理代码”从“业务实现代码”中分离出来。将上面最后一段伪码改为如下伪码:
if 一切正常:
#业务实现代码
else:
alert 输入不合法
goto retry
上面代码中的“if 块”依然不可表示,因为一切正常是很抽象的,无法转换为计算机可识别的代码。在这种情形下,Python 提出了一种假设,如果程序可以顺利完成,那就“一切正常”,把系统的业务实现代码放在 try 块中定义,把所有的异常处理逻辑放在 except 块中进行处理。
下面是 Python 异常处理机制的语法结构:
try:
#业务实现代码
...
except (Error1, Error2, ...) as e:
alert 输入不合法
goto retry
如果在执行 try 块里的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给 Python 解释器,这个过程被称为引发异常。
当 Python 解释器收到异常对象时,会寻找能处理该异常对象的 except 块,如果找到合适的 except 块,则把该异常对象交给该 except 块处理,这个过程被称为捕获异常。如果 Python 解释器找不到捕获异常的 except 块,则运行时环境终止,Python 解释器也将退出。
不管程序代码块是否处于 try 块中,甚至包括 except 块中的代码,只要执行该代码块时出现了异常,系统总会自动生成一个 Error 对象。如果程序没有为这段代码定义任何的 except 块,则 Python 解释器无法找到处理该异常的 except 块,程序就在此退出,这就是前面看到的例子程序在遇到异常时退出的情形。
下面使用异常处理机制来改写前面章节中五子棋游戏中用户下棋部分的代码:
inputStr = input("请输入您下棋的坐标,应以x,y的格式:\n")
while inputStr != None :
try:
# 将用户输入的字符串以逗号(,)作为分隔符,分隔成2个字符串
x_str, y_str = inputStr.split(sep = ",")
# 如果要下棋的点不为空
if board[int(y_str) - 1][int(x_str) - 1] != "╋":
inputStr = input("您输入的坐标点已有棋子了,请重新输入\n")
continue
# 把对应的列表元素赋为"●"。
board[int(y_str) - 1][int(x_str) - 1] = "●"
except Exception:
inputStr = input("您输入的坐标不合法,请重新输入,下棋坐标应以x,y的格式\n")
continue
...
上面程序把处理用户输入字符串的代码都放在 try 块里执行,只要用户输入的字符串不是有效的坐标值(包括字母不能正确解析,没有逗号不能正确解析,解析出来的坐标引起数组越界……),系统就将引发一个异常对象,并把这个异常对象交给对应的 except 块处理。
except 块的处理方式是向用户提示坐标不合法,然后使用 continue 忽略本次循环剩下的代码,开始执行下一次循环。这就保证了该五子棋游戏有足够的容错性,即用户可以随意输入,程序不会因为用户输入不合法而突然退出,程序会向用户提示输入不合法,让用户再次输入。
异常类的继承体系
当 Python 解释器接收到异常对象时,如何为该异常对象寻找 except 块呢?注意上面程序中 except 块的 except Exception:,这意味着每个 except 块都是专门用于处理该异常类及其子类的异常实例。
当 Python 解释器接收到异常对象后,会依次判断该异常对象是否是 except 块后的异常类或其子类的实例,如果是,Python 解释器将调用该 except 块来处理该异常;否则,再次拿该异常对象和下一个 except 块里的异常类进行比较。
Python 异常捕获流程示意图如图 1 所示:
图 1 Python 异常捕获流程示意图
从图 1 中可以看出,在 try 块后可以有多个 except 块,这是为了针对不同的异常类提供不同的异常处理方式。当程序发生不同的意外情况时,系统会生成不同的异常对象,Python 解释器就会根据该异常对象所属的异常类来决定使用哪个 except 块来处理该异常。
通过在 try 块后提供多个 except 块可以无须在异常处理块中使用 if 判断异常类型,但依然可以针对不同的异常类型提供相应的处理逻辑,从而提供更细致、更有条理的异常处理逻辑。
从图 1 中可以看出,在通常情况下,如果 try 块被执行一次,则 try 块后只有一个 except 块会被执行,不可能有多个 except 块被执行。除非在循环中使用了 continue 开始下一次循环,下一次循环又重新运行了 try 块,这才可能导致多个 except 块被执行。
Python 的所有异常类都从 BaseException 派生而来,提供了丰富的异常类,这些异常类之间有严格的继承关系,图 2 显示了 Python 的常见异常类之间的继承关系。
图 2 Python 的常见异常类之间的继承关系
从图 2 中可以看出,Python 的所有异常类的基类是 BaseException,但如果用户要实现自定义异常,则不应该继承这个基类,而是应该继承 Exception 类。
BaseException 的主要子类就是 Exception,不管是系统的异常类,还是用户自定义的异常类,都应该从 Exception 派生。
下面看几个简单的异常捕获的例子:
import sys
try:
a = int(sys.argv[1])
b = int(sys.argv[2])
c = a / b
print("您输入的两个数相除的结果是:", c )
except IndexError:
print("索引错误:运行程序时输入的参数个数不够")
except ValueError:
print("数值错误:程序只能接收整数参数")
except ArithmeticError:
print("算术错误")
except Exception:
print("未知异常")
上面程序,导入了 sys 模块,并通过 sys 模块的 argv 列表来获取运行 Python 程序时提供的参数。其中 sys.argv[0] 通常代表正在运行的 Python 程序名,sys.argv[1] 代表运行程序所提供的第一个参数,sys.argv[2] 代表运行程序所提供的第二个参数……依此类推。
Python 用 import 例来导入模块,关于模块和导入模块会在后续章节进行详细讲解。
上面程序针对 IndexError、ValueError、ArithmeticError 类型的异常,提供了专门的异常处理逻辑。该程序运行时的异常处理逻辑可能有如下几种情形:
- 如果在运行该程序时输入的参数不够,将会发生索引错误,Python 将调用 IndexError 对应的 except 块处理该异常。
- 如果在运行该程序时输入的参数不是数字,而是字母,将发生数值错误,Python 将调用 ValueError 对应的 except 块处理该异常。
- 如果在运行该程序时输入的第二个参数是 0,将发生除 0 异常,Python 将调用 ArithmeticError 对应的 except 块处理该异常。
- 如果在程序运行时出现其他异常,该异常对象总是 Exception 类或其子类的实例,Python 将调用 Exception 对应的 except 块处理该异常。
上面程序中的三种异常,都是非常常见的运行时异常,读者应该记住这些异常,并掌握在哪些情况下可能出现这些异常。
正如在前面程序中所看到的,程序总是把对应 Exception 类的 except 块放在最后,这是为什么呢?想一下图 1 所示的 Python 异常捕获流程,可能你就会明白,如果把 Exception 类对应的 except 块排在其他 except 块的前面,Python 解释器将直接进入该 except 块(因为所有的异常对象都是 Exception 或其子类的实例),而排在它后面的 except 块将永远也不会获得执行的机会。
实际上,在进行异常捕获时不仅应该把 Exception 类对应的 except 块放在最后,而且所有父类异常的 except 块都应该排在子类异常的 except 块的后面( 即:先处理小异常,再处理大异常)。
虽然 Python 语法没有要求,但在实际编程时一定要记住先捕获小异常,再捕获大异常。
多异常捕获
Python 的一个 except 块可以捕获多种类型的异常。
在使用一个 except 块捕获多种类型的异常时,只要将多个异常类用圆括号括起来,中间用逗号隔开即可,其实就是构建多个异常类的元组。
下面程序示范了 Python 的多异常捕获:
import sys
try:
a = int(sys.argv[1])
b = int(sys.argv[2])
c = a / b
print("您输入的两个数相除的结果是:", c )
except (IndexError, ValueError, ArithmeticError):
print("程序发生了数组越界、数字格式异常、算术异常之一")
except:
print("未知异常")
上面程序中第 7 行代码使用了(IndexError, ValueError, ArithmeticError)来指定所捕获的异常类型,这就表明该 except 块可以同时捕获这三种类型的异常。
看上面程序中第 9 行代码,只有 except 关键字,并未指定具体要捕获的异常类型,这种省略异常类的 except 语句也是合法的,它表示可捕获所有类型的异常,一般会作为异常捕获的最后一个 except 块。
访问异常信息
如果程序需要在 except 块中访问异常对象的相关信息,则可通过为异常对象声明变量来实现。
当 Python 解释器决定调用某个 except 块来处理该异常对象时,会将异常对象赋值给 except 块后的异常变量,程序即可通过该变量来获得异常对象的相关信息。
所有的异常对象都包含了如下几个常用属性和方法:
- args:该属性返回异常的错误编号和描述字符串。
- errno:该属性返回异常的错误编号。
- strerror:该属性返回异常的描述宇符串。
- with_traceback():通过该方法可处理异常的传播轨迹信息。
下面例子演示了程序如何访问异常信息:
def foo():
try:
fis = open("a.txt");
except Exception as e:
# 访问异常的错误编号和详细信息
print(e.args)
# 访问异常的错误编号
print(e.errno)
# 访问异常的详细信息
print(e.strerror)
foo()
从上面程序可以看出,如果要访问异常对象,只要在单个异常类或异常类元组(多异常捕获)之后使用 as 再加上异常变量即可。
在 Python 的早期版本中,直接在单个异常类或异常类元组(多异常捕获)之后添加异常变量,中间用逗号隔开即可。
上面程序调用了 Exception 对象的 args 属性(该属性相当于同时返回 errno 属性和 strerror 属性)访问异常的错误编号和详细信息。运行上面程序,会看到如下运行结果:
(2, 'No such file or directory')
2
No such file or directory
从上面的运行结果可以看到,由于程序尝试打开的文件不存在,因此引发的异常错误编号为 2,异常详细信息为:No such file or directory。
关于如何处理异常的传播轨迹信息,后续章节还有更详细的介绍,此处暂不详细讲解。
上面程序中使用 open() 方法来打开一个文件,用于读取磁盘文件的内容。关于该 open() 方法后续章节会做详细介绍。
else 块
在 Python 的异常处理流程中还可添加一个 else 块,当 try 块没有出现异常时,程序会执行 else 块。例如如下程序:
s = input('请输入除数:')
try:
result = 20 / int(s)
print('20除以%s的结果是: %g' % (s , result))
except ValueError:
print('值错误,您必须输入数值')
except ArithmeticError:
print('算术错误,您不能输入0')
else:
print('没有出现异常')
上面程序为异常处理流程添加了 else 块,当程序中的 try 块没有出现异常时,程序就会执行 else 块。运行上面程序,如果用户输入导致程序中的 try 块出现了异常,则运行结果如下:
请输入除数:a
值错误,您必须输入数值
如果用户输入让程序中的 try 块顺利完成,则运行结果如下:
请输入除数:3
20 除以3 的结果是:6.66667
没有出现异常
看到这里,可能有读者觉得奇怪:既然只有当 try 块没有异常时才会执行 else 块,那么直接把 else 块的代码放在 try 块的代码的后面不就行了?
实际上大部分语言的异常处理都没有 else 块,它们确实是将 else 块的代码直接放在 try 块的代码的后面的,因为对于大部分场景而言,直接将 else 块的代码放在 try 块的代码的后面即可。
但 Python 的异常处理使用 else 块绝不是多余的语法,当 try 块没有异常,而 else 块有异常时,就能体现出 else 块的作用了。例如如下程序:
def else_test():
s = input('请输入除数:')
result = 20 / int(s)
print('20除以%s的结果是: %g' % (s , result))
def right_main():
try:
print('try块的代码,没有异常')
except:
print('程序出现异常')
else:
# 将else_test放在else块中
else_test()
def wrong_main():
try:
print('try块的代码,没有异常')
# 将else_test放在try块代码的后面
else_test()
except:
print('程序出现异常')
wrong_main()
right_main()
上面程序中定义了一个 else_test() 函数,该函数在运行时需要接收用户输入的参数,随着用户输入数据的不同可能导致异常。接下来程序定义了 right_main() 和 wrong_main() 两个函数,其中 right_main() 将 else_test() 函数放在 else 块内;而 wrong_main() 将 else_test() 函数放在 try 块的代码的后面。
正如上面所介绍的,当 try 块和 else 块都没有异常时,将 else_test() 函数放在 try 块的代码的后面和放在 else 块中没有任何区别。例如,如果用户输入的数据没有导致程序出现异常,则将看到程序产生如下输出结果:
try块的代码,没有异常
请输入除数:4
20除以4的结果是: 5
try块的代码,没有异常
请输入除数:4
20除以4的结果是: 5
但如果用户输入的数据让 else_test() 函数出现异常(try 块依然没有任何异常),此时程序就会产生如下输出结果:
try块的代码,没有异常
请输入除数:0
程序出现异常
try块的代码,没有异常
请输入除数:0
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\1.py", line 21, in <module>
right_main()
File "C:\Users\mengma\Desktop\1.py", line 12, in right_main
else_test()
File "C:\Users\mengma\Desktop\1.py", line 3, in else_test
result = 20 / int(s)
ZeroDivisionError: division by zero
对比上面两个输出结果,用户输入的都是 0,这样都会导致 else_test() 函数出现异常。如果将 else_test() 函数放在 try 块的代码的后面,此时 else_test() 函数运行产生的异常将会被 try 块对应的 except 捕获,这正是 Python 异常处理机制的执行流程:但如果将 else_test() 函数放在 else 块中,当 else_test() 函数出现异常时,程序没有 except 块来处理该异常,该异常将会传播给 Python 解释器,导致程序中止。
对比上面两个输出结果,不难发现,放在 else 块中的代码所引发的异常不会被 except 块捕获。
所以,如果希望某段代码的异常能被后面的 except 块捕获,那么就应该将这段代码放在 try 块的代码之后;如果希望某段代码的异常能向外传播(不被 except 块捕获),那么就应该将这段代码放在 else 块中。