600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 【Python】详解 try-except-else-finally 语句 —— 异常处理完全解读 (上)

【Python】详解 try-except-else-finally 语句 —— 异常处理完全解读 (上)

时间:2023-05-20 04:00:01

相关推荐

【Python】详解 try-except-else-finally 语句 —— 异常处理完全解读 (上)

目录

一、绪论 (introduction)

二、异常捕获(exception catching)

2.1try-except 语句

2.1.1 基本用法

2.1.2 指定异常类型

2.1.3 小结

2.2try-except-else 语句

2.3try-except-else-finally 语句

2.4 小结

三、异常抛出 (exception raising)

四、异常自定义 (exception customizing)

五、预定义的清理行为 (with statement)

六、断言 (asserting)

七、小结 (summary)

八、彩蛋 —— finally 和 return 谁的优先级更高?(选读)

相关文章【Python】详解 with 语句 (上下文管理器) —— 异常处理完全解读(下)

一、绪论 (introduction)

不同于语法错法错误 (解析错误),调试 Python 程序时,即便语句或表达式的语法正确,也可能在执行时引发错误。在执行时检测到的错误称为异常。Python 使用被称为异常特殊对象来管理程序执行期间发生的错误。每当发生让 Python 不知所措的错误时,它都会创建一个异常对象。

异常虽不一定会导致严重后果,但大多数异常并不会被程序处理。当 Python 脚本发生异常时,程序将终止执行,并显示各种回溯 (Traceback)信息。Traceback 是 Python 错误信息的报告,类似于其他编程语言中的 stack trace、stack traceback、backtrac 等。Traceback 的前一部分以堆栈回溯的形式显示发生异常时的上下文,并由语法分析器指示出错的行,而最后一行则声明程序的错误类型信息。

尤其是在读写文件时,很多地方都可能导致错误发生。例如,试图读取一个不存在的文件或目录时,将得到一个找不到文件的错误 (FileNotFoundError):

>>> fin = open('test.py')Traceback (most recent call last):File "<pyshell#1>", line 1, in <module>fin = open('test.py')FileNotFoundError: [Errno 2] No such file or directory: 'test.py'

关于异常的原因,一方面,可能源自个人疏忽与考虑不周,此时需要根据异常 Traceback 到出错位置并分析改正;另一方面,有些异常无法预料或不可避免,此时可选择捕获异常并处理,从而避免程序的意外终止或崩溃

二、异常捕获(exception catching)

使用try 语句代码块是最基本的异常捕获和处理方法,其常见的搭配形式 (关键词组合) 有:

try-excepttry-except-elsetry-except-else-finally

当然,对于关键词的选用搭配,实质还是取决于个人需求,以下将依次进行说明:

2.1try-except 语句

2.1.1 基本用法

try-except 语句是最基础而重要的部分,其基本语法规则为:

try:# 执行要尝试 (try) 的代码except:# 执行应对异常发生时的代码

try-except 语句用于检测 try 子句(块) 中的错误,从而令 except 语句(块) 捕获异常信息并作出应对和处理。具体而言,Python从 try 子句开始执行,若一切正常,则跳过 except 子句;若发生异常,则跳出 try 子句,执行 except 子句

延续上节例子 —— 捕获读取一个不存在的文件/目录的异常:

>>> try:fin = open('test.py') # 不存在的文件print('Everything went well!') # 打印顺利运行提示信息except:print('Something went wrong!') # 处理异常方式:打印错误提示信息Something went wrong!

可见异常被捕获了,IDLE 并未打印 Traceback 信息,而是打印了我们自定义的 except 子句中的错误提示信息。然而,本例中的except 子句仅仅是简单地提示了错误。实际上,可以根据需求设计更多具有实用修正/弥补功能的except 子句。

另一方面,若文件/目录存在,则将顺利执行完try 子句并跳过 except 子句:

>>> try:fin = open('train.py') # 实际存在的文件print('Everything went well!') # 打印顺利运行提示信息except:print('Something went wrong!') # 处理异常方式:打印错误提示信息Everything went well!

2.1.2 指定异常类型

因为 except 子句默认捕获的异常类型是 Exception,所以 except 子句总是捕获所有异常。

>>> try:fin = open('test.py') print('Everything went well!') except Exception: # 不指定 Exception 也一样print('Something went wrong!') Something went wrong!

但若有特殊需要,也可指定 except 子句捕获的异常类型,例如:

>>> try:fin = open('test.py')print('Everything went well!')except FileNotFoundError:print('Something went wrong!')Something went wrong!

关于异常类型指定,既可以后知后觉 —— 根据 Trackback 指出的错误,也可以先知先觉 —— 查文档选定以防不测。但注意,倘若发生了未指定到的异常类型 (通常源于误指定或漏指定导致异常类型不匹配),则异常仍会发生:

>>> try:fin = open('test.py')print('Everything went well!')except KeyError: # 虽然可以 catch KeyError, 但发生了 FileNotFoundErrorprint('Something went wrong!')Traceback (most recent call last):File "<pyshell#19>", line 2, in <module>fin = open('test.py')FileNotFoundError: [Errno 2] No such file or directory: 'test.py'

因此,若仅需要捕获异常,不指定 except 语句捕获的异常类型将更为保险和省事 (毕竟异常类型辣么多...)。

与此同时,若要捕获处理指定类型异常,一方面,可以将需要捕获的异常类型全都放在同一个 tuple 中

>>> try:fin = open('eval.py')print('Everything goes well!')except (FileExistsError, FileNotFoundError): # 异常类型 tupleprint('There is a FileExistsError or FileNotFoundError!')There is a FileExistsError or FileNotFoundError!

这样做的优点是简洁明了,统一捕获异常处理;缺点是不能够“特事特办” ——except 子句的异常处理将缺乏针对性

为实现对多种不同的特定类型异常的分别捕获处理,可以令一个 try 语句对应多个 except 语句,例如:

>>> try:fin = open('eval.py')print('Everything goes well!')except FileExistsError: # 捕获特定类型异常print('There is a FileExistsError!')except FileNotFoundError: # 捕获特定类型异常print('There is a FileNotFoundError!')There is a FileNotFoundError!

多个 except 子句串行执行,对于异常 “有则捕获,无则通过”。注意,若发生的异常和 except 子句指定的异常类是同一个类或者是其基类,则可以兼容并照常捕获处理(比如异常指定为 Exception 时可捕获大部分的异常,因为所有内置的非系统退出类异常/用户自定义异常都派生自此类),但反之不成立(except 子句指定的异常是实际发生异常的子类/派生类时则无法捕获)。

此外,还可以使用 as 关键字指定 except 语句所捕获异常的别名,

>>> try:fin = open('eval.py')print('Everything goes well!')except FileNotFoundError as error: # as 关键字指定异常别名 print("Error information:{0}".format(error))Error information:[Errno 2] No such file or directory: 'eval.py'

2.1.3 小结

总而言之,常见的用法仍是:令前面的 except 子句指定特定类型异常,令最后一个 except 子句忽略异常名以用作通配符,然后打印一个未知错误信息,并用raise 关键字抛出异常。如下所示:

>>> import sys>>> try:fin = open('eval.py')print('Everything goes well!')except FileExistsError as error:print("Error information:{0}".format(error))except:print("Unexpected error:", sys.exc_info()[0])raise # 抛出异常Unexpected error: <class 'FileNotFoundError'>Traceback (most recent call last):File "<pyshell#36>", line 2, in <module>fin = open('eval.py')FileNotFoundError: [Errno 2] No such file or directory: 'eval.py'

以上即为篇幅最大、最为基本而详实的用法说明。

2.2try-except-else 语句

try 语句除了可以后跟一至多个 except 子句,还可以选用 else 子句。若使用 else 子句,则必须将其后接于except 子句后,且只能有一个 else 子句。else 子句将在 try 子句未发生任何异常时执行

>>> try:fin = open('oneline.txt')print('Everything goes well!')except FileExistsError:print('There is a FileExistsError!')except FileNotFoundError:print('There is a FileNotFoundError!')else:print(fin.readlines()) # 读取一行fin.close() # 关闭/释放文件对象 finEverything goes well!['I Love Python!']

上例顺利执行 try 语句,读取了一个只有一行的 txt 文件,打印出成功读取信息,并因此跳过各个 except 子句。然后,执行 else 子句,读取 txt 文件的一行内容并打印之,最后关闭 fin 文件对象。

通常,使用 else 子句比将所有语句都放在 try 语句中灵活性更强,效果更好,因为如此可避免一些难以预料且except 无法捕获的异常。异常处理并不仅仅处理那些直接发生在 try 语句中的异常,而且还能处理子句中调用的函数 (甚至间接调用的函数) 里抛出的异常。例如:

>>> def wrong():num = 6 / 0>>> try:wrong()except ZeroDivisionError as error:print('Handling run-time error:', error)Handling run-time error: division by zero

总之,对于在 try子句不引发异常时必须执行的代码而言,else 子句很有用。

2.3try-except-else-finally 语句

除了 else 子句,还有另一个常用可选子句 —— finally 子句。若使用 finally 子句,则必须将其后接于最后,且只能有一个finally 子句。无论异常有无发生,finally 子句都将执行。因此,finally 子句常用于存放一些必定要执行的内容或操作,例如:

>>> try:fin = open('oneline.txt')print('Everything goes well!')except FileExistsError:print('There is a FileExistsError!')except FileNotFoundError:print('There is a FileNotFoundError!')else:print(fin.readlines())fin.close()finally:print("Operations are Finished!")Everything goes well!['I Love Python!']Operations are Finished!

finally 子句常用于定义无论在任何情况下都会执行的清理行为。若一个异常在 try 子句里 (或在 except 子句和 else 子句里) 被抛出,而又没有任何的 except 子句将其捕获,那么该异常将会在 finally 子句执行后被抛出。例如:

>>> def divide(x, y):try:result = x / yexcept ZeroDivisionError:print("division by zero!")else:print("the result is", result)finally:print("executing finally clause")>>> divide('6', '3')executing finally clauseTraceback (most recent call last):File "<pyshell#18>", line 1, in <module>divide('6', '3')File "<pyshell#17>", line 3, in divideresult = x / yTypeError: unsupported operand type(s) for /: 'str' and 'str'

2.4 小结

try-except-else-finally 语句简图:

三、异常抛出 (exception raising)

Python 通过raise 语句强制抛出一个指定异常,其语法格式为:

raise [Exception [, args [, traceback]]]

raise 的唯一参数即要抛出的指定异常,该参数必须是一个异常实例或异常类 (即派生自 Exception 的类)。若传递的是一个异常类,它将通过调用无参数的构造函数实现隐式实例化。

若只想确定是否抛出了异常而并不想去处理它,那么一个无参数的raise 语句便可将当前在处理的异常再次抛出。例如:

>>> try:raise NameError('HiThere') # 指定抛出异常名及其 Trackback 提示语except NameError:print('An exception flew by!')An exception flew by!# ------------------------------------------------------------------------------>>> try:raise NameError('Hello') # 指定抛出异常名及其 Trackback 提示语except NameError:print('An exception flew by!')raise # 再次抛出, 对比上例An exception flew by!Traceback (most recent call last):File "<pyshell#1>", line 2, in <module>raise NameError('Hello')NameError: Hello

如果令最后一个 raise语句指定另一个类型的异常,则 Traceback 将按发生顺序显示这些串联的异常信息:

>>> try:raise NameError('Hello')except NameError:print('An exception flew by!')raise KeyErrorAn exception flew by!Traceback (most recent call last):File "<pyshell#8>", line 2, in <module>raise NameError('Hello')NameError: HelloDuring handling of the above exception, another exception occurred:Traceback (most recent call last):File "<pyshell#8>", line 5, in <module>raise KeyErrorKeyError

此外,隐式的异常上下文还可通过使用raise ... from ...语句来补充显式的原因,限于篇幅不作详述。

总之,使用 raise 语句可引发内置异常,通常用于测试异常处理程序或报告错误条件

四、异常自定义 (exception customizing)

在 Python 中,所有异常必须为一个派生自 BaseException的类的实例 (BaseException 是所有内置异常的基类)。在带有指定一个特定类的except 子句的 try 语句中,该子句将处理派生自 BaseException类的异常类 (但也有例外)。通过子类化创建的两个不相关异常类永远不等效的,即便二者名称相同。

除了 Python 内置异常类,还可以将内置异常类子类化以定义新的异常。因为BaseException 类不应被用户自定义类直接继承,所以鼓励从 Exception 类或其子类来派生新的异常

例如,可以直接或间接继承 Exception 类实现一个自定义的异常类:

>>> class MyError(Exception):''' 自定义异常类需要继承自 Exception 类 '''def __init__(self, value): # 重写父类 Exception 的构造方法 __init__() 以覆盖之self.value = valuedef __str__(self):return repr(self.value)>>> try:raise MyError(6)except MyError as error:print('My exception occurred, value:', error.value)My exception occurred, value: 6

自定义异常类可执行任何其他类能执行的任何操作,但实现时通常只提供许多属性和少量方法,以允许处理程序为异常提取有关错误的信息的同时确保简洁性。

此外,在创建可能引发多个不同异常的模块时,通常的做法是为该模块定义的各种异常创建一个基类 (作为基础的异常类),然后基于该基类为不同的错误条件创建不同的子类。例如:

class Error(Exception):"""Base class for exceptions in this module."""passclass InputError(Error):"""Exception raised for errors in the input.Attributes:expression -- input expression in which the error occurredmessage -- explanation of the error"""def __init__(self, expression, message):self.expression = expressionself.message = messageclass TransitionError(Error):"""Raised when an operation attempts a state transition that's notallowed.Attributes:previous -- state at beginning of transitionnext -- attempted new statemessage -- explanation of why the specific transition is not allowed"""def __init__(self, previous, next, message):self.previous = previousself.next = nextself.message = message

大多数异常的名称都定义为以 Error 结尾,类似于标准异常的命名。

五、预定义的清理行为 (with statement)

在 Python 中,一些对象定义了在不再需要该对象时要执行的标准清理行为,无论使用该对象的操作成败与否。例如,下面的示例它尝试打开一个文件并打印其内容:

for line in open("test.txt"):print(line, end="")

这段代码的问题在于,当执行完毕后,文件会在一段不确定的时间内保持打开状态,而未被显式地关闭!这在简单脚本中无所谓,但对较大的应用程序而言可能是个问题。

因为with 语句可以实现资源的精确分配与释放,所有在本例场景下,with 语句能够保证诸如文件之类的对象在使用完后,一定会正确地执行其清理方法,例如:

>>> with open("train.txt") as f:for line in f:print(line, end="")

从而,执行完语句后,既便上述代码在处理过程中出现问题,也能够确保文件 f 总是被关闭

关于with 语句 实现的 上下文管理器 (Context manager),详见《【Python】详解 with 语句 (上下文管理器) —— 异常处理与完全解读(下)》。

六、断言 (asserting)

除了上述引发异常的方式,Python 中还有一个 assert 断言语句能够触发异常。assert 语句常用于判断表达式,并在表达式条件为 False 时触发异常(准确地说是表达式的 bool 逻辑值为 False 时)。其语法格式为:

assert expression

实质等价于:

if not expression:raise AssertionError

例如:

>>> assert True>>> assert False # 表达式的 bool 逻辑值为 False 将引发异常Traceback (most recent call last):File "<pyshell#22>", line 1, in <module>assert FalseAssertionError>>> assert 1 > 0>>> assert 1 < 0 # 表达式的 bool 逻辑值为 False 将引发异常Traceback (most recent call last):File "<pyshell#25>", line 1, in <module>assert 1 < 0AssertionError

与此同时,assert 后也可指定参数:

assert expression [, arguments]

实质等价于:

if not expression:raise AssertionError(arguments)

例如:

>>> assert 1 == 0, '1 is not equal to 0'Traceback (most recent call last):File "<pyshell#27>", line 1, in <module>assert 1 == 0, '1 is not equal to 0'AssertionError: 1 is not equal to 0

总之,assert 语句能够在条件不满足程序运行的情况下直接返回错误,而不必等待程序运行后出现崩溃的情况。例如,某代码只能在 Linux 下运行,于是可以先判断当前系统是否符合条件:

import sysassert ('linux' in sys.platform), "该代码只能在 Linux 下执行"# 接下来要执行的代码

七、小结 (summary)

本文中主要说明的Python 异常相关常见关键字 (还有 assert 用于断言):

八、彩蛋 —— finally 和 return 谁的优先级更高?(选读)

已知 try-finally 语句中,无论 try 子句正常执行还是引发异常,finally 子句最终都能被执行 (用于收尾)。又知,return 作为函数的出口,每逢 return 语句,函数都将结束运行。那么问题来了,如果同时存在 finally 和 return,谁的优先级更高,Python 解释器将如何抉择?测试一下:

>>> def test(): # 在 try 子句与 finally 子句均书写 return 语句try:return "try"finally:return "finally">>> test()'finally'

可见,Python 解释器忽略了 try 子句中的 return 语句 (此处的 return 并非函数终点),以确保 finally 子句的执行。

但其实,try 子句中的 return 语句并非被忽视。已知函数未显式定义 return 语句时,将隐式地返回 None (返回值为 None)。那么,若 finally 子句中未显式定义 return 语句时,是否应返回 None 呢?验证一下:

>>> def val(): # 只在 try 子句中书写 return 语句try:return "try"finally:...# ... 等同于 pass>>> val()'try'# ------------------------------------------------------------------------------>>> def val(): # 只在 try 子句中书写 return 语句try:return "try"finally:print("finally")>>> val()finally'try'# ------------------------------------------------------------------------------>>> def val(): # 在 try 子句与 finally 子句中均不书写 return 语句try:print("try")finally:print("finally")>>> val()tryfinally

可见,未在 finally 子句中显式定义 return 语句时,try 子句中的 return 语句还是有效的。总而言之,在包含 try-finally 语句的函数中:

若finally 子句中显式定义了return 语句,那么该 return 语句会直接覆盖 try 子句中的 return 语句 (如果有);

若finally 子句中未显式定义return 语句,那么 try 子句中的 return 语句 (如果有) 将生效

参考文献:

《Think Python》《Python Immediate》《Python Cookbook》

/zh-cn/3.6/tutorial/errors.html?highlight=异常

/zh-cn/3.6/library/exceptions.html?highlight=异常

Python3 错误和异常 | 菜鸟教程

Python 异常处理 | 菜鸟教程

百度安全验证

Python3 assert(断言) | 菜鸟教程

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。