第 6 章 异常和文件处理
Description: Python from novice to pro
Keywords: Python, Dive Into Python, tutorial, object-oriented, programming, documentation, book, free

# 深入 Python :Dive Into Python 中文版 Python 从新手到专家 [Dip_5.4b_CPyUG_Release] | Find:

## 第 6 章 异常和文件处理

在本章中，将研究异常、文件对象、`for` 循环、`os` 和 `sys` 模块等内容。如果你已经在其它编程语言中使用过异常，你可以简单看看第一部分来了解 Python 的语法。但是本章其它的内容仍需仔细研读。

## 6.1. 异常处理

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

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

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

- 使用不存在的字典关键字将引发 `KeyError` 异常。
- 搜索列表中不存在的值将引发 `ValueError` 异常。
- 调用不存在的方法将引发 `AttributeError` 异常。
- 引用不存在的变量将引发 `NameError` 异常。
- 未强制转换就混用数据类型将引发 `TypeError` 异常。

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

然而，一个异常不一定会引起程序的完全崩溃。当异常引发时，可以被处理 掉。有时候一个异常实际是因为代码中的 bug (比如使用一个不存在的变量)，但是许多时候，一个异常是可以预见的。如果你打开一个文件，它可能不存在。如果你连接一个数据库，它可能不可连接或没有访问所需的正确的安全证书。如果知道一行代码可能会引发异常，你应该使用一个 `try...except` 块来处理异常。

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

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

| 使用内置 `open` 函数，我们可以试着打开一个文件来读取 (在下一节有关于 `open` 的更多内容)。但是那个文件不存在，所以这样就引发 `IOError` 异常。因为我们没有提供任何显式的对 `IOError` 异常的检查，Python 仅仅打印出某个关于发生了什么的调试信息，然后终止。
| 我们试图打开同样不存在的文件，但是这次我们在一个 `try...except` 内来执行它。
| 当 `open` 方法引发 `IOError` 异常时，我们已经准备好处理它了。`except IOError:` 行捕捉异常，接着执行我们自已的代码块，这个代码块在本例中只是打印出更令人愉快的错误信息。
| 一旦异常被处理了，处理通常在 `try...except` 块之后的第一行继续进行。注意这一行将总是打印出来，无论异常是否发生。如果在你的根目录下确实有一个叫 `notthere` 的文件，对 `open` 的调用将成功，`except` 子句将忽略，并且最后一行仍将执行。

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

### 6.1.1. 为其他用途使用异常

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

你也能通过创建一个从内置的 `Exception` 类继承的类定义你自己的异常，然后使用 `raise` 命令引发你的异常。如果你对此感兴趣，请看进一步阅读的部分。

下面的例子演示了如何使用异常支持特定平台功能。代码来自 `getpass` 模块，一个从用户获得口令的封装模块。获得口令在 UNIX、Windows 和 Mac OS 平台上的实现是不同的，但是这个代码封装了所有的不同之处。

### 例 6.2. 支持特定平台功能

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

| `termios` 是 UNIX 独有的一个模块，它提供了对于输入终端的底层控制。如果这个模块无效 (因为它不在你的系统上，或你的系统不支持它)，则导入失败，Python 引发我们捕捉的 `ImportError` 异常。
| OK，我们没有 `termios`，所以让我们试试 `msvcrt`，它是 Windows 独有的一个模块，可以提供在 Microsoft Visual C++ 运行服务中的许多有用的函数的一个API。如果导入失败，Python 会引发我们捕捉的 `ImportError` 异常。
| 如果前两个不能工作，我们试着从 `EasyDialogs` 导入一个函数，它是 Mac OS 独有的一个模块，提供了各种各样类型的弹出对话框。再一次，如果导入失败，Python 会引发一个我们捕捉的 `ImportError` 异常。
| 这些平台特定的模块没有一个有效 (有可能，因为 Python 已经移植到了许多不同的平台上了)，所以我们需要回头使用一个缺省口令输入函数 (这个函数定义在 `getpass` 模块中的别的地方)。注意我们在这里所做的：我们将函数 `default_getpass` 赋给变量 `getpass`。如果你读了官方 `getpass` 文档，它会告诉你 `getpass` 模块定义了一个 `getpass` 函数。它是这样做的：通过绑定 `getpass` 到正确的函数来适应你的平台。然后当你调用 `getpass` 函数时，你实际上调用了平台特定的函数，是这段代码已经为你设置好的。你不需要知道或关心你的代码正运行在何种平台上；只要调用 `getpass`，则它总能正确处理。
| 一个 `try...except` 块可以有一条 `else` 子句，就像 `if` 语句。如果在 `try` 块中没有异常引发，然后 `else` 子句被执行。在本例中，那就意味着如果 `from EasyDialogs import AskPassword` 导入可工作，所以我们应该绑定 `getpass` 到 `AskPassword` 函数。其它每个 `try...except` 块有着相似的 `else` 子句，当我们发现一个 `import` 可用时，就绑定 `getpass` 到适合的函数。

### 进一步阅读

- Python Tutorial 讨论了异常，包括定义和引发你自已的异常，以及一次处理多个异常。
- Python Library Reference 总结了所有内置异常。
- Python Library Reference 提供了 getpass 模块的文档。
- Python Library Reference 提供了 `traceback` 模块 的文档，这个模块在异常引发之后，提供了底层的对异常属性的处理。
- Python Reference Manual 讨论了 `try...except` 块 的内部工作方式。