8. 错误和异常¶

    语法错误,也被称作解析错误,也许是你学习 Python 过程中最常见抱怨:

    语法分析器指出错误行,并且在检测到错误的位置前面显示一个小“箭头”。错误是由箭头 前面 的标记引起的(或者至少是这么检测的):这个例子中,关键字 print 被发现存在错误,因为它前面少了一个冒号( )。错误会输出文件名和行号,所以如果是从脚本输入的你就知道去哪里检查错误了。

    8.2. 异常¶

    即使一条语句或表达式在语法上是正确的,当试图执行它时也可能会引发错误。运行期检测到的错误称为 异常,并且程序不会无条件的崩溃:很快,你将学到如何在 Python 程序中处理它们。然而,大多数异常都不会被程序处理,像这里展示的一样最终会产生一个错误信息:

    1. >>> 10 * (1/0)
    2. Traceback (most recent call last):
    3. File "<stdin>", line 1, in ?
    4. ZeroDivisionError: integer division or modulo by zero
    5. >>> 4 + spam*3
    6. Traceback (most recent call last):
    7. File "<stdin>", line 1, in ?
    8. NameError: name 'spam' is not defined
    9. >>> '2' + 2
    10. Traceback (most recent call last):
    11. File "<stdin>", line 1, in ?
    12. TypeError: cannot concatenate 'str' and 'int' objects

    错误信息的最后一行指出发生了什么错误。异常也有不同的类型,异常类型做为错误信息的一部分显示出来:示例中的异常分别为 除错误( ZeroDivisionError ),命名错误( )和类型错误( TypeError )。打印错误信息时,异常的类型作为异常的内置名显示。对于所有的内置异常都是如此,不过用户自定义异常就不一定了(尽管这是一个很有用的约定)。标准异常名是内置的标识(没有保留关键字)。

    这一行后一部分是关于该异常类型的详细说明,这意味着它的内容依赖于异常类型。

    错误信息的前半部分以堆栈的形式列出异常发生的位置。通常在堆栈中列出了源代码行,然而,来自标准输入的源码不会显示出来。

    列出了内置异常和它们的含义。

    通过编程处理选择的异常是可行的。看一下下面的例子:它会一直要求用户输入,直到输入一个合法的整数为止,但允许用户中断这个程序(使用 Control-C 或系统支持的任何方法)。注意:用户产生的中断会引发一个 KeyboardInterrupt 异常:

    1. >>> while True:
    2. ... try:
    3. ... x = int(raw_input("Please enter a number: "))
    4. ... break
    5. ... except ValueError:
    6. ... print "Oops! That was no valid number. Try again..."
    7. ...

    语句按如下方式工作:

    • 首先,执行 try 子句(在 try 和 关键字之间的部分)。

    • 如果没有异常发生,except 子句在 try 语句执行完毕后就被忽略了。

    • 如果在 try 子句执行过程中发生了异常,那么该子句其余的部分就会被忽略。

    如果异常匹配于 关键字后面指定的异常类型,

    然后继续执行 try 语句之后的代码。

    • 如果发生了一个异常,在 子句中没有与之匹配的分支,

    它就会传递到上一级 try 语句中。

    如果最终仍找不到对应的处理语句,它就成为一个 未处理异常,终止程序运行,显示提示信息。

    一个 语句可能包含多个 except 子句,分别指定处理不同的异常。至多只会有一个分支被执行。异常处理程序只会处理对应的 try 子句中发生的异常,在同一个 try 语句中,其他子句中发生的异常则不作处理。一个 except 子句可以在括号中列出多个异常的名字,例如:

    1. ... except (RuntimeError, TypeError, NameError):
    2. ... pass

    注意,此元组周围的括号是必需的,因为 except ValueError, e: 是旧式的写法,在现代 Python 中通常写成 (如下所述)。为了保持向后兼容性,旧式语法仍然是支持的。这意味着 except RuntimeError, TypeError 不等同于 except (RuntimeError, TypeError): 而等同于 , 这应该不是你想要的。

    最后一个 except 子句可以省略异常名称,以作为通配符使用。你需要慎用此法,因为它会轻易隐藏一个实际的程序错误!可以使用这种方法打印一条错误信息,然后重新抛出异常(允许调用者处理这个异常):

    1. import sys
    2.  
    3. try:
    4. f = open('myfile.txt')
    5. s = f.readline()
    6. i = int(s.strip())
    7. except IOError as e:
    8. print "I/O error({0}): {1}".format(e.errno, e.strerror)
    9. except ValueError:
    10. print "Could not convert data to an integer."
    11. except:
    12. print "Unexpected error:", sys.exc_info()[0]
    13. raise

    except 语句可以带有一个 else子句 ,该子句只能出现在所有 except 子句之后。当 try 语句没有抛出异常时,需要执行一些代码,可以使用这个子句。例如

    使用 子句比在 try 子句中附加代码要好,因为这样可以避免 … except 意外的截获本来不属于它们保护的那些代码抛出的异常。

    发生异常时,可能会有一个附属值,作为异常的 参数 存在。这个参数是否存在、是什么类型,依赖于异常的类型。

    在异常名(列表)之后,也可以为 except 子句指定一个变量。这个变量绑定于一个异常实例,它存储在 instance.args 的参数中。为了方便起见,异常实例定义了 ,这样就可以直接访问过打印参数而不必引用 .args。这种做法不受鼓励。相反,更好的做法是给异常传递一个参数(如果要传递多个参数,可以传递一个元组),把它绑定到 message 属性。一旦异常发生,它会在抛出前绑定所有指定的属性:

    1. >>> try:
    2. ... raise Exception('spam', 'eggs')
    3. ... except Exception as inst:
    4. ... print inst.args # arguments stored in .args
    5. ... print inst # __str__ allows args to be printed directly
    6. ... x, y = inst.args
    7. ... print 'x =', x
    8. ... print 'y =', y
    9. ...
    10. <type 'exceptions.Exception'>
    11. ('spam', 'eggs')
    12. ('spam', 'eggs')
    13. x = spam
    14. y = eggs

    对于那些未处理的异常,如果一个它们带有参数,那么就会被作为异常信息的最后部分(“详情”)打印出来。

    异常处理器不仅仅处理那些在 try 子句中立刻发生的异常,也会处理那些 try 子句中调用的函数内部发生的异常。例如:

    1. >>> def this_fails():
    2. ... x = 1/0
    3. ...
    4. >>> try:
    5. ... this_fails()
    6. ... except ZeroDivisionError as detail:
    7. ... print 'Handling run-time error:', detail
    8. ...
    9. Handling run-time error: int division or modulo by zero

    8.4. 抛出异常¶

    语句允许程序员强制抛出一个指定的异常。例如:

    1. >>> raise NameError('HiThere')
    2. Traceback (most recent call last):
    3. File "<stdin>", line 1, in ?
    4. NameError: HiThere

    如果你需要明确一个异常是否抛出,但不想处理它,raise 语句可以让你很简单的重新抛出该异常:

    1. >>> try:
    2. ... raise NameError('HiThere')
    3. ... except NameError:
    4. ... print 'An exception flew by!'
    5. ... raise
    6. ...
    7. An exception flew by!
    8. Traceback (most recent call last):
    9. File "<stdin>", line 2, in ?
    10. NameError: HiThere

    在程序中可以通过创建新的异常类型来命名自己的异常(Python 类的内容请参见 )。异常类通常应该直接或间接的从 Exception 类派生,例如:

    在这个例子中,Exception 默认的 被覆盖。新的方式简单的创建 value 属性。这就替换了原来创建 args 属性的方式。

    异常类中可以定义任何其它类中可以定义的东西,但是通常为了保持简单,只在其中加入几个属性信息,以供异常处理句柄提取。如果一个新创建的模块中需要抛出几种不同的错误时,一个通常的作法是为该模块定义一个异常基类,然后针对不同的错误类型派生出对应的异常子类:

    1. class Error(Exception):
    2. """Base class for exceptions in this module."""
    3. pass
    4.  
    5. class InputError(Error):
    6. """Exception raised for errors in the input.
    7.  
    8. Attributes:
    9. expr -- input expression in which the error occurred
    10. msg -- explanation of the error
    11. """
    12.  
    13. def __init__(self, expr, msg):
    14. self.expr = expr
    15.  
    16. class TransitionError(Error):
    17. """Raised when an operation attempts a state transition that's not
    18. allowed.
    19.  
    20. Attributes:
    21. prev -- state at beginning of transition
    22. next -- attempted new state
    23. msg -- explanation of why the specific transition is not allowed
    24. """
    25.  
    26. def __init__(self, prev, next, msg):
    27. self.prev = prev
    28. self.next = next
    29. self.msg = msg

    与标准异常相似,大多数异常的命名都以 “Error” 结尾。

    很多标准模块中都定义了自己的异常,用以报告在他们所定义的函数中可能发生的错误。关于类的进一步信息请参见 一章。

    8.6. 定义清理行为¶

    try 语句还有另一个可选的子句,目的在于定义在任何情况下都一定要执行的功能。例如:

    1. >>> try:
    2. ... raise KeyboardInterrupt
    3. ... finally:
    4. ... print 'Goodbye, world!'
    5. ...
    6. Goodbye, world!
    7. KeyboardInterrupt
    8. Traceback (most recent call last):
    9. File "<stdin>", line 2, in ?

    不管有没有发生异常,finally子句 在程序离开 后都一定会被执行。当 try 语句中发生了未被 捕获的异常(或者它发生在 except 或 子句中),在 finally 子句执行完后它会被重新抛出。 语句经由 break, 或 return 语句退出也一样会执行 子句。以下是一个更复杂些的例子(在同 一个 try 语句中的 和 finally 子句的工作方式与 Python 2.5 一样):

    1. >>> def divide(x, y):
    2. ... try:
    3. ... result = x / y
    4. ... except ZeroDivisionError:
    5. ... print "division by zero!"
    6. ... else:
    7. ... print "result is", result
    8. ... finally:
    9. ... print "executing finally clause"
    10. ...
    11. >>> divide(2, 1)
    12. result is 2
    13. executing finally clause
    14. >>> divide(2, 0)
    15. division by zero!
    16. executing finally clause
    17. >>> divide("2", "1")
    18. executing finally clause
    19. Traceback (most recent call last):
    20. File "<stdin>", line 1, in ?
    21. File "<stdin>", line 3, in divide
    22. TypeError: unsupported operand type(s) for /: 'str' and 'str'

    如你所见, 子句在任何情况下都会执行。TypeError 在两个字符串相除的时候抛出,未被 except 子句捕获,因此在 子句执行完毕后重新抛出。

    在真实场景的应用程序中,finally 子句用于释放外部资源(文件或网络连接之类的),无论它们的使用过程中是否出错。

    有些对象定义了标准的清理行为,无论对象操作是否成功,不再需要该对象的时候就会起作用。以下示例尝试打开文件并把内容打印到屏幕上:

    1. for line in open("myfile.txt"):
    2. print line

    这段代码的问题在于在代码执行完后没有立即关闭打开的文件。这在简单的脚本里没什么,但是大型应用程序就会出问题。 语句使得文件之类的对象可以确保总能及时准确地进行清理:

    语句执行后,文件 f 总会被关闭,即使是在处理文件中的数据时出错也一样。其它对象是否提供了预定义的清理行为要查看它们的文档。