处理异常
当你第一次发现显示大回溯作为输出的异常时,你可能会尝试捕获每个错误以防止发生这种情况。
如果你的任务是到达火星,导航系统上的文本显示“发生错误”时,该怎么办? 假设没有任何其他信息或上下文,只有闪烁的红灯和错误文本。 作为一名开发人员,站在程序的另一方角度看问题会很有用:当出错时,用户可以做什么?
虽然此模块介绍了如何通过捕获异常来处理异常,但并不需要始终捕获异常。 有时允许引发异常会很有用,其他调用方可以处理错误。
try 和 except 块
让我们使用导航器示例来创建代码,以便为火星任务打开配置文件。 配置文件可能会遇到各种问题,因此在问题出现时准确报告问题非常重要。 我们知道,如果文件或目录不存在,则会引发 FileNotFoundError
。 如果我们想要处理此异常,可以使用 try
和 except
块:
try:
open('config.txt')
except FileNotFoundError:
print("Couldn't find the config.txt file!")
Couldn't find the config.txt file!
在 try
关键字之后,添加可能引发异常的代码。 接下来,添加 except
关键字与可能的异常,后跟在该条件发生时需要运行的任何代码。 由于 config.txt 在系统中不存在,因此 Python 将打印配置文件不存在。 try
和 except
块以及有用的消息会阻止回溯,并仍通知用户有关此问题的信息。
虽然文件不存在很常见,但它并不是你可能发现的唯一错误。 无效的文件权限会阻止读取文件,即使文件存在也是如此。 让我们在 Visual Studio Code 中创建一个名为 config.py 的新 Python 文件。 将用于查找和读取导航系统配置文件的以下代码添加到该文件:
def main():
try:
configuration = open('config.txt')
except FileNotFoundError:
print("Couldn't find the config.txt file!")
if __name__ == '__main__':
main()
接下来,创建一个名为 config.txt 的目录。 尝试调用 config.py 文件以查看新错误,该错误应如下所示:
python3 config.py
Traceback (most recent call last):
File "/tmp/config.py", line 9, in <module>
main()
File "/tmp/config.py", line 3, in main
configuration = open('config.txt')
IsADirectoryError: [Errno 21] Is a directory: 'config.txt'
处理此错误的一种无用方法是捕获所有可能的异常,以防出现回溯。 若要了解捕获所有异常不可行的原因,请通过在新创建的 config.py 文件中更新 main()
函数来尝试:
def main():
try:
configuration = open('config.txt')
except Exception:
print("Couldn't find the config.txt file!")
现在,在具有不正确权限的 config.txt 目录所在的同一位置再次运行该代码:
python3 config.py
Couldn't find the config.txt file!
现在的问题是错误消息不正确。 此目录的确存在,但它具有不同的权限,并且 Python 无法读取。 处理软件错误时,遇到以下错误可能会让人沮丧:
- 错误不指出真正的问题。
- 错误给出与实际问题不匹配的输出。
- 错误不提示可以执行哪些操作来解决问题。
让我们修复这段代码,以解决所有这些问题。 恢复为捕获 FileNotFoundError
,然后添加另一个 except
块以捕获 PermissionError
:
def main():
try:
configuration = open('config.txt')
except FileNotFoundError:
print("Couldn't find the config.txt file!")
except IsADirectoryError:
print("Found config.txt but it is a directory, couldn't read it")
现在在 config.txt 目录所在的位置再次运行它:
python3 config.py
Found config.txt but couldn't read it
现在删除 config.txt 目录,以确保改为访问第一个 except
块:
rm -f config.txt
python3 config.py
Couldn't find the config.txt file!
如果错误具有类似的性质,并且无需单独处理,则可以通过在 except
行中使用括号将异常归为一组。 例如,如果导航系统负载过大,并且文件系统变得太忙,则很有必要同时捕获 BlockingIOError
和 TimeOutError
:
def main():
try:
configuration = open('config.txt')
except FileNotFoundError:
print("Couldn't find the config.txt file!")
except IsADirectoryError:
print("Found config.txt but it is a directory, couldn't read it")
except (BlockingIOError, TimeoutError):
print("Filesystem under heavy load, can't complete reading configuration file")
提示
即使可以将异常归为一组,也只有在不需要单独处理异常时才这样做。 避免将多个异常归为一组以提供普通的错误消息。
如果需要访问与异常相关的错误,则必须更新 except
行以包含 as
关键字。 如果异常太过普通,则这种方法就非常方便并且错误消息会很有用:
try:
open("mars.jpg")
except FileNotFoundError as err:
print("Got a problem trying to read the file:", err)
Got a problem trying to read the file: [Errno 2] No such file or directory: 'mars.jpg'
在本例中,as err
意味着 err
成为一个变量,而异常对象则作为值。 然后,它使用此值来打印与异常相关的错误消息。 使用此方法的另一个原因是直接访问该错误的属性。 例如,如果捕获到更加普通的 OSError
异常,该异常是 FilenotFoundError
和 PermissionError
的父异常,则可以通过 .errno
属性来区分它们:
try:
open("config.txt")
except OSError as err:
if err.errno == 2:
print("Couldn't find the config.txt file!")
elif err.errno == 13:
print("Found config.txt but couldn't read it")
Couldn't find the config.txt file!
始终尝试使用可以让代码的易读性最高并且有助于将来维护的方法。 有时,当发生错误时,需要使用更少的代码来提供更好的用户体验。