.. include:: ../LINKS.rst .. _tut-errors: ******************************** 错误和异常 ******************************** 到现在为止, 没有更多的提及错误信息, 但是当你在尝试这些例子时, 或多或少会碰到一些. 这里 (至少) 有两种可以分辨的错误: *syntax error* 和 *exception* , 按中文来说, 就是语法错误和异常. .. _tut-syntaxerrors: 语法错误 ====================== 语法错误, 也可以认为是解析时错误, 这是在你学习 Python 过程中最有可能碰到的:: >>> while True print('Hello world') File "", line 1, in ? while True print('Hello world') ^ SyntaxError: invalid syntax 解析器会重复出错的那行, 然后显示一个小箭头, 指出探测到错误时最早的那个点. 错误一般是由箭头所指的地方导致 (或者至少是此处被探测到): 在这个例子中, 错误是在 :func:`print` 函数这里被发现的, 因为在它之前少了一个冒号 (``':'``). 文件的名称与行号会被打印出来, 以便于你能找到一个脚本中导致错误的地方. .. _tut-exceptions: 异常 =============== 尽管语句或表达式语法上是没有问题的, 它同样也会在尝试运行时导致一个错误. 在执行时探测到的错误被成为 *exception* , 也就是异常, 但它并不是致命的问题: 你将会很快学到如何在 Python 程序中处理它们. 大多数异常并不会被程序处理, 不过, 导致错误的信息会被显示出来:: >>> 10 * (1/0) Traceback (most recent call last): File "", line 1, in ? ZeroDivisionError: int division or modulo by zero >>> 4 + spam*3 Traceback (most recent call last): File "", line 1, in ? NameError: name 'spam' is not defined >>> '2' + 2 Traceback (most recent call last): File "", line 1, in ? TypeError: Can't convert 'int' object to str implicitly 每个错误信息的最后一行或说明发生了什么. 异常会有很多的类型, 而这个类型会作为消息的一部分打印出来: 在此处的例子中的类型有 :exc:`ZeroDivisionError`, :exc:`NameError` 和 :exc:`TypeError`. 作为异常类型被输出的字符串其实是发生内建异常的名称. 对于所有内建异常都是那样的, 但是对于用户自定义的异常, 则可能不是这样 (尽管有某些约定). 标准异常的名字是内建的标识符 (但并不是关键字). 改行剩下的部分则提供更详细的信息, 是什么样的异常, 是怎么导致的. 错误消息的前面部分指出了异常发生的上下文, 以 stack traceback (栈追踪) 的方式显示. 一般来说列出了源代码的行数; 但是并不会显示从标准输入得到的行数. :ref:`bltin-exceptions` 列出了内建的异常和它们的意义. .. _tut-handling: 处理异常 ============================ 写程序来处理异常是可能的. 看看下面的例子, 它请求用户输入一个合法的整数, 但是也允许用户来中断程序 (使用 :kbd:`Control-C` 或任何操作系统支持的); 注意, 用户生成的中断是通过产生异常 :exc:`KeyboardInterrupt`:: >>> while True: ... try: ... x = int(input("Please enter a number: ")) ... break ... except ValueError: ... print("Oops! That was no valid number. Try again...") ... :keyword:`try` 语句像下面这样使用. * 首先, *try clause* (在 :keyword:`try` 和 :keyword:`except` 之间的语句) 将被执行. * 如果没有异常发生, *except clause* 将被跳过, :keyword:`try` 语句就算执行完了. * 如果在 try 语句执行时, 出现了一个异常, 该语句的剩下部分将被跳过. 然后如果它的类型匹配到了 :keyword:`except` 后面的异常名, 那么该异常的语句将被执行, 而执行完后会运行 :keyword:`try` 后面的问题. * 如果一个异常发生时并没有匹配到 except 语句中的异常名, 那么它就被传到 :keyword:`try` 语句外面; 如果没有处理, 那么它就是 *unhandled exception* 并且将会像前面那样给出一个消息然后执行. 一个 :keyword:`try` 语句可以有多于一条的 except 语句, 用以指定不同的异常. 但至多只有一个会被执行. Handler 仅仅处理在相应 try 语句中的异常, 而不是在同一 :keyword:`try` 语句中的其他 Handler. 一个异常的语句可以同时包括多个异常名, 但需要用括号括起来, 比如:: ... except (RuntimeError, TypeError, NameError): ... pass 最后的异常段可以忽略异常的名字, 用以处理其他的情况. 使用这个时需要特别注意, 因为它很容易屏蔽了程序中的错误! 它也用于输出错误消息, 然后重新产生异常 (让调用者处理该异常):: import sys try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except IOError as err: print("I/O error: {0}".format(err)) except ValueError: print("Could not convert data to an integer.") except: print("Unexpected error:", sys.exc_info()[0]) raise :keyword:`try` ... :keyword:`except` 语句可以有一个可选的 *else* 语句, 在这里, 必须要放在所有 except 语句后面. 它常用于没有产生异常时必须执行的语句. 例如:: for arg in sys.argv[1:]: try: f = open(arg, 'r') except IOError: print('cannot open', arg) else: print(arg, 'has', len(f.readlines()), 'lines') f.close() 使用 :keyword:`else` 比额外的添加代码到 :keyword:`try` 中要好, 因为这样可以避免偶然的捕获一个异常, 但却不是由于我们保护的代码所抛出的. 当一个异常发生了, 它可能有相关的值, 这也就是所谓的异常的参数. 该参数是否出现及其类型依赖于异常的类型. 在 except 语句中可以在异常名后指定一个变量. 变量会绑定值这个异常的实例上, 并且把参数存于 ``instance.args``. 为了方便, 异常的实例会定义 :meth:`__str__` 来直接将参数打印出来, 而不用引用 ``.args``. 当然也可以在产生异常前, 首先实例化一个异常, 然后把需要的属性绑定给它. :: >>> try: ... raise Exception('spam', 'eggs') ... except Exception as inst: ... print(type(inst)) # the exception instance ... print(inst.args) # arguments stored in .args ... print(inst) # __str__ allows args to be printed directly, ... # but may be overridden in exception subclasses ... x, y = inst.args # unpack args ... print('x =', x) ... print('y =', y) ... ('spam', 'eggs') ('spam', 'eggs') x = spam y = eggs 如果一个异常有参数, 它们将作为异常消息的最后一部分打印出来. 异常的 handler 处理的异常, 不仅仅是 try 语句中那些直接的异常, 也可以是在此处调用的函数所产生的异常. 例如: :: >>> def this_fails(): ... x = 1/0 ... >>> try: ... this_fails() ... except ZeroDivisionError as err: ... print('Handling run-time error:', err) ... Handling run-time error: int division or modulo by zero .. _tut-raising: 抛出异常 =========================== :keyword:`raise` 语句允许程序员强制一个特定的异常的发生. 举个例子: :: >>> raise NameError('HiThere') Traceback (most recent call last): File "", line 1, in ? NameError: HiThere 给 :keyword:`raise` 的唯一参数表示产生的异常. 这必须是一个异常实例或类 (派生自 :class:`Exception` 的类). 如果你需要决定产生一个异常, 但是不准备处理它, 那么一个简单的方式就是, 重新抛出异常: :: >>> try: ... raise NameError('HiThere') ... except NameError: ... print('An exception flew by!') ... raise ... An exception flew by! Traceback (most recent call last): File "", line 2, in ? NameError: HiThere .. _tut-userexceptions: 自定义异常 ================================== 程序中可以通过定义一个新的异常类 (更多的类请参考 :ref:`tut-classes`) 来命名它们自己的异常. 异常需要从 :exc:`Exception` 类派生, 既可以是直接也可以是间接. 例如: :: >>> class MyError(Exception): ... def __init__(self, value): ... self.value = value ... def __str__(self): ... return repr(self.value) ... >>> try: ... raise MyError(2*2) ... except MyError as e: ... print('My exception occurred, value:', e.value) ... My exception occurred, value: 4 >>> raise MyError('oops!') Traceback (most recent call last): File "", line 1, in ? __main__.MyError: 'oops!' 在这个例子中, :class:`Exception` 的默认方法 :meth:`__init__` 被覆写了. 现在新的异常类可以像其他的类一样做任何的事, 但是常常会保持简单性, 仅仅提供一些可以被 handler 处理的异常信息. 当创建一个模块时, 可能会有多种不同的异常, 一种常用的做法就是, 创建一个基类, 然后派生出各种不同的异常: :: class Error(Exception): """Base class for exceptions in this module.""" pass class InputError(Error): """Exception raised for errors in the input. Attributes: expression -- input expression in which the error occurred message -- explanation of the error """ def __init__(self, expression, message): self.expression = expression self.message = message class TransitionError(Error): """Raised when an operation attempts a state transition that's not allowed. Attributes: previous -- state at beginning of transition next -- attempted new state message -- explanation of why the specific transition is not allowed """ def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message 大多数异常定义时都会以 "Error" 结尾, 就像标准异常的命名. 大多数标准模块都定义了它们自己的异常, 用于报告在它们定义的函数中发生的错误. 关于更多类的信息请参考 :ref:`tut-classes`. .. _tut-cleanup: 定义清理动作 ====================================== :keyword:`try` 语句有另一种可选的从句, 用于定义一些扫尾的工作, 此处定义的语句在任何情况下都会被执行. 例如: :: >>> try: ... raise KeyboardInterrupt ... finally: ... print('Goodbye, world!') ... Goodbye, world! Traceback (most recent call last): File "", line 2, in ? KeyboardInterrupt 一个 *finally 语句*总是在离开 :keyword:`try` 语句前被执行, 而无论此处有无异常发生. 当一个异常在 :keyword:`try` 中产生, 但是并没有被 :keyword:`except` 处理 (或者它发生在 :keyword:`except` 或 :keyword:`else` 语句中), 那么在 :keyword:`finally` 语句执行后会被重新抛出. :keyword:`finally` 语句在其他语句要退出 :keyword:`try` 时也会被执行, 像是使用 :keyword:`break`, :keyword:`continue` 或者 :keyword:`return`. 一个更复杂的例子: :: >>> def divide(x, y): ... try: ... result = x / y ... except ZeroDivisionError: ... print("division by zero!") ... else: ... print("result is", result) ... finally: ... print("executing finally clause") ... >>> divide(2, 1) result is 2.0 executing finally clause >>> divide(2, 0) division by zero! executing finally clause >>> divide("2", "1") executing finally clause Traceback (most recent call last): File "", line 1, in ? File "", line 3, in divide TypeError: unsupported operand type(s) for /: 'str' and 'str' 正如你所看到的, :keyword:`finally` 语句在任何情况下都被执行了. 由于将两个字符串相除而产生的 :exc:`TypeError` 并没有被 :keyword:`except` 语句处理, 因此在执行 :keyword:`finally` 后被重新抛出. 在真正的应用中, :keyword:`finally` 是非常有用的, 特别是释放额外的资源 (类似文件或网络连接), 无论此资源是否成功使用. .. _tut-cleanup-with: 预定义的清理动作 ============================================ 有些对象定义了标准的清理工作, 特别是对象不再需要时, 无论对其使用的操作是否成功. 看看下面的例子, 它尝试打开一个文件并输出内容至屏幕. :: for line in open("myfile.txt"): print(line) 前面这段代码的问题在于, 在此代码成功执行后, 文件依然被打开着. 在简单的脚本中这可能不是什么问题, 但是对于更大的应用来说却是个问题. :keyword:`with` 语句就允许像文件这样的对象在使用后会被正常的清理掉. :: with open("myfile.txt") as f: for line in f: print(line) 在执行该语句后, 文件 *f* 就会被关闭, 就算是在读取时碰到了问题. 像文件这样的对象, 总会提供预定义的清理工作, 更多的可以参考它们的文档. .. seealso:: (^.^) - 原文: http://docs.python.org/py3k/tutorial/errors.html - 初译: `刘鑫`_ - 精译: `DocsPy3zh`_ - 校对: `Zoom.Quiet`_ - 复审: