<< >>

diveintopython.org |

Python for experienced programmers

## 3.9. 处理异常

象许多面向对象语言一样，Python具有异常处理，通过使用 `try...except` 块。C++，Java，和Delphi也有这个机制；VB根据它的声明将在版本7中实现它，Powerbuilder将在版本8中实现。在Python中的语法同C++，Java和Delphi相似。

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

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

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

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

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

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

    >>> 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` 子句将忽略，并且最后一行仍将执行。

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

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

例 3.18. 支持特定平台功能

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

    # 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` 异常。
| 哦，我们没有 `termios`，所以让我们试试 `msvcrt`，它是一个Windows特定模块，可以提供在Microsoft Visual C++运行服务中的许多有用的函数的一个API。如果导入失败，Python会引发我们捕捉的 `ImportError` 异常。
| 如果前两个不能工作，我们试着从 `EasyDialogs` 导入一个函数，它是一个MacOS特定模块，提供了各种各样类型的弹出对话框。再一次，如果导入失败，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 提供了`traceback` 模块的文档，这个模块在异常引发之后，提供了底层的对异常属性的处理。
- Python Reference Manual 讨论了`try...except` 块的内部工作方式。

脚注 |

[5] Or, as some marketroids would put it, your program would perform an illegal action. Whatever.(译注，谁告诉我这句话如何翻译？)

« 私有函数 | 文件对象 »