3.10. 处理异常

象许多面向对象语言一样,Python具有异常处理,通过使用 try...except 块来实现。

Note
Python使用 try...except 来处理异常,使用 raise 来引发异常。Java和C++使用 try...catch 来处理异常,使用 throw 来引发异常。

如果你已经了解了异常的全部知识,你可以跳过这节。如果你一直在一种松散语言下编程,或者你使用着真正的语言但没有用过异常,本节非常重要。

异常在Python中无处不在;实际上在标准Python库中的每个模块都使用了它们,并且Python自已会在许多不同的情况下引发它们。在整本书中你已经再三看到它们了。

在这些情况下,我们都在简单使用Python IDE:一个错误发生了,异常被打印出来(根据你的IDE,有意地以一种刺眼的红色形式表示),并且就这些。这叫做未处理异常;当异常被引发时,没有代码来显式地关注它和处理它,所以异常被传给置在Python中的缺省的处理,它会输出一些调试信息并且终止运行。在IDE中,这不是什么大事,但是如果发生在你真正的Python程序运行的时候,整个程序将会终止。[6]

然而,一个异常不一定会引起程序的完全崩溃。当异常引发时,可以被处理掉。有时候一个异常实际是因为代码中的bug(比如使用一个不存在的变量),但是许多时候,一个异常是可以预计的。如果知道一行代码可能会引发异常(象打开一个可能不存在的文件,或连到一个可能不能处理的数据库),你应该使用一个 try...except 块来处理异常。

例 3.21. 打开一个不存在的文件

>>> fsock = open("/notthere", "r")      1
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
IOError: [Errno 2] No such file or directory: '/notthere'
>>> try:
...     fsock = open("/notthere")       2
... except IOError:                     3
...     print "The file does not exist, exiting gracefully"
... print "This line will always print" 4
The file does not exist, exiting gracefully
This line will always print
1

使用内置 open 函数,我们可以试着打开一个文件来读取(在下一节有更多的内容关于 open)。但是那个文件不存在,所以这样就引发 IOError 异常。因为我们没有提供任何显式的对 IOError 异常的检查,Python仅仅打印出某个关于发生了什么的调试信息,然后终止。

2

我们试图打开同样不存在的文件,但是这次我们在一个 try...except 内来执行它。

3

open 方法引发 IOError 异常时,我们已经准备好处理它了。 except IOError: 行捕捉异常接着执行我们自已的代码块,这个代码块在本例中只是打印出更令人愉快的错误信息。

4

一旦异常被处理了,处理通常在 try...except 块之后的第一行继续进行。注意这一行将总是打印出来,无论异常是否发生。如果在你的根目录下确实有一个叫 notthere 的文件,对 open 的调用将成功,except 子句将忽略,并且最后一行仍将执行。

异常可能看不去不友好(毕竟,如果你不捕捉异常,整个程序将崩溃),但是考虑一下别的方法。你宁愿找回对于不存在文件的不可用的文件对象吗?不管怎么样你都得检查它的有效性,而且如果你忘记了,你的程序将会在下面某个地方给出奇怪的错误,这样你将不得不追溯到源程序。我确信你做过这种事;这可并不有趣。使用异常,一发生错误,你就可以在问题的源头通过标准的方法来处理它们。

除了处理实际的错误条件之外,对于异常还有许多其它的用处。在标准Python库中一个普通的用法就是试着导入一个模块,然后检查是否它能使用。导入一个并不存在的模块将引发一个 ImportError 异常。你可以使用这种方法来定义多级别的功能,依靠在运行时哪个模块是有效的,或支持多种平台(即平台特定代码被分离到不同的模块中)。

例 3.22. 支持特定平台功能

这个代码来源于 getpass 模块,一个从用户得到口令的封装模块。得到口令在UNIX,Windows,和MacOS平台上的实现是不同的,但是这个代码封装了所有不同。

  # Bind the name getpass to the appropriate function
  try:
      import termios, TERMIOS                     1
  except ImportError:
      try:
          import msvcrt                           2
      except ImportError:
          try:
              from EasyDialogs import AskPassword 3
          except ImportError:
              getpass = default_getpass           4
          else:                                   5
              getpass = AskPassword
      else:
          getpass = win_getpass
  else:
      getpass = unix_getpass
1

termios 是一个UNIX特定模块,它提供了对于输入终端的底层控制。如果这个模块无效(因为它不在你的系统上,或你的系统不支持它),则导入失败,Python引发我们捕捉的 ImportError 异常。

2

哦,我们没有 termios,所以让我们试试 msvcrt,它是一个Windows特定模块,可以提供在Microsoft Visual C++运行服务中的许多有用的函数的一个API。如果导入失败,Python会引发我们捕捉的 ImportError 异常。

3

如果前两个不能工作,我们试着从 EasyDialogs 导入一个函数,它是一个MacOS特定模块,提供了各种各样类型的弹出对话框。再一次,如果导入失败,Python会引发一个我们捕捉的 ImportError 异常。

4

这些平台特定的模块没有一个有效(有可能,因为Python已经移植到了许多不同的平台上了),所以我们需要回头使用一个缺省口令输入函数(这个函数定义在 getpass 模块中的别的地方)。注意,我们在这里做的:我们将函数 default_getpass 赋给变量 getpass。如果你读了官方 getpass 文档,它会告诉你 getpass 模块定义了一个 getpass 函数。它是这样做的:通过绑定 getpass 到正确的函数来适应你的平台。然后当你调用 getpass 函数时,你实际上调用了平台特定的函数,是这段代码已经为你设置好的。你不需要知道或关心你的代码正运行在何种平台上;只要调用 getpass,则它总能正确处理。

5

一个 try...except 块可以有一条 else 子句,就象 if 语句。如果在 try 块中没有异常引发,然后 else 子句被执行。在本例中,那就意味着如果 from EasyDialogs import AskPassword 导入可工作,所以我们应该绑定 getpassAskPassword 函数。其它每个 try...except 块有着相似的 else 子句,当我们找到一个 import 可用时,来绑定 getpass 到适合的函数 。


进一步阅读

脚注

[6] 或者象某些信息所显示的,你的程序执行了一个非法动作。