处理异常

已完成

当你第一次发现显示大回溯作为输出的异常时,你可能会尝试捕获每个错误以防止发生这种情况。

如果你的任务是到达火星,导航系统上的文本显示“发生错误”时,该怎么办? 假设没有任何其他信息或上下文,只有闪烁的红灯和错误文本。 作为一名开发人员,站在程序的另一方角度看问题会很有用:当出错时,用户可以做什么?

虽然此模块介绍了如何通过捕获异常来处理异常,但并不需要始终捕获异常。 有时允许引发异常会很有用,其他调用方可以处理错误。

try 和 except 块

让我们使用导航器示例来创建代码,以便为火星任务打开配置文件。 配置文件可能会遇到各种问题,因此在问题出现时准确报告问题非常重要。 我们知道,如果文件或目录不存在,则会引发 FileNotFoundError。 如果我们想要处理此异常,可以使用 tryexcept 块:

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 将打印配置文件不存在。 tryexcept 块以及有用的消息会阻止回溯,并仍通知用户有关此问题的信息。

虽然文件不存在很常见,但它并不是你可能发现的唯一错误。 无效的文件权限会阻止读取文件,即使文件存在也是如此。 让我们在 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 行中使用括号将异常归为一组。 例如,如果导航系统负载过大,并且文件系统变得太忙,则很有必要同时捕获 BlockingIOErrorTimeOutError

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 异常,该异常是 FilenotFoundErrorPermissionError 的父异常,则可以通过 .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!

始终尝试使用可以让代码的易读性最高并且有助于将来维护的方法。 有时,当发生错误时,需要使用更少的代码来提供更好的用户体验。