8. 错误和异常¶

至今为止还没有进一步的谈论过错误信�,�过在你已�试验过的那些例�中,�能已��到过一些。Python 中(至少)有两�错误:语法错误和异常( syntax errors 和 exceptions )。

8.1. 语法错误¶

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

>>> while True print 'Hello world'
  File "<stdin>", line 1, in ?
    while True print 'Hello world'
                   ^
SyntaxError: invalid syntax

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

8.2. 异常¶

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

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

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

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

错误信�的��部分以堆栈的形�列出异常�生的�置。通常在堆栈中列出了�代�行,然而,�自标准输入的���会显示出�。

Built-in Exceptions 列出了内置异常和它们的�义。

8.3. 异常处�¶

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

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

try 语�按如下方�工作:

  • 首先,执行 try å­�å�¥(在 try å’Œ except 关键字之间的部分)。

  • 如果没有异常å�‘生,except å­�å�¥åœ¨ try 语å�¥æ‰§è¡Œå®Œæ¯•å�Žå°±è¢«å¿½ç•¥äº†ã€‚

  • 如果在 try å­�å�¥æ‰§è¡Œè¿‡ç¨‹ä¸­å�‘生了异常,那么该å­�å�¥å…¶ä½™çš„部分就会被忽略。

    如果异常匹�于 except 关键字��指定的异常类型,

    就执行对应的 except ��。

    然�继续执行 try 语�之�的代�。

  • 如果å�‘生了一个异常,在 except å­�å�¥ä¸­æ²¡æœ‰ä¸Žä¹‹åŒ¹é…�的分支,

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

    如果最终�找�到对应的处�语�,它就�为一个 未处�异常,终止程��行,显示�示信�。

一个 try 语��能包�多个 except ��,分别指定处���的异常。至多�会有一个分支被执行。异常处�程��会处�对应的 try ��中�生的异常,在�一个 try 语�中,其他��中�生的异常则�作处�。一个 except ���以在括�中列出多个异常的�字,例如:

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

注�,此元组周围的括�是必需的,因为 except ValueError, e: 是旧�的写法,在现代 Python 中通常写� except ValueError, e: (如下所述)。为了����兼容性,旧�语法�然是支�的。这�味� except RuntimeError, TypeError �等�于 except (RuntimeError, TypeError): 而等�于 except RuntimeError as TypeError: , 这应该�是你想�的。

最�一个 except ���以�略异常�称,以作为通�符使用。你需�慎用此法,因为它会轻易��一个实际的程�错误��以使用这�方法打�一�错误信�,然��新抛出异常(�许调用者处�这个异常):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as e:
    print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error:", sys.exc_info()[0]
    raise

try ... except 语��以带有一个 else�� ,该���能出现在所有 except ��之�。当 try 语�没有抛出异常时,需�执行一些代�,�以使用这个��。例如

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()

使用 else ��比在 try ��中附加代��好,因为这样�以�� try ... except �外的截获本��属于它们�护的那些代�抛出的异常。

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

在异常�(列表)之�,也�以为 except ��指定一个��。这个��绑定于一个异常实例,它存储在 instance.args 的�数中。为了方便起�,异常实例定义了 __str__() ,这样就�以直接访问过打��数而�必引用 .args。这��法��鼓励。相�,更好的�法是给异常传递一个�数(如果�传递多个�数,�以传递一个元组),把它绑定到 message 属性。一旦异常�生,它会在抛出�绑定所有指定的属性:

>>> 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
...    x, y = inst.args
...    print 'x =', x
...    print 'y =', y
...
<type 'exceptions.Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

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

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

>>> def this_fails():
...     x = 1/0
...
>>> try:
...     this_fails()
... except ZeroDivisionError as detail:
...     print 'Handling run-time error:', detail
...
Handling run-time error: int division or modulo by zero

8.4. 抛出异常¶

raise 语��许程�员强制抛出一个指定的异常。例如:

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

�抛出的异常由 raise 的唯一�数标识。它必需是一个异常实例或异常类(继承自 Exception 的类)。

如果你需�明确一个异常是�抛出,但�想处�它,raise 语��以让你很简�的�新抛出该异常:

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print 'An exception flew by!'
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

8.5. 用户自定义异常¶

在程�中�以通过创建新的异常类型�命�自己的异常(Python 类的内容请�� 类 )。异常类通常应该直接或间接的从 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 "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

在这个例�中,Exception 默认的 __init__() 被覆盖。新的方�简�的创建 value 属性。这就替�了原�创建 args 属性的方�。

异常类中�以定义任何其它类中�以定义的东西,但是通常为了��简�,�在其中加入几个属性信�,以供异常处��柄��。如果一个新创建的模�中需�抛出几���的错误时,一个通常的作法是为该模�定义一个异常基类,然�针对��的错误类型派生出对应的异常�类:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expr -- input expression in which the error occurred
        msg  -- explanation of the error
    """

    def __init__(self, expr, msg):
        self.expr = expr
        self.msg = msg

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        prev -- state at beginning of transition
        next -- attempted new state
        msg  -- explanation of why the specific transition is not allowed
    """

    def __init__(self, prev, next, msg):
        self.prev = prev
        self.next = next
        self.msg = msg

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

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

8.6. 定义清�行为¶

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

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print 'Goodbye, world!'
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in ?

�管有没有�生异常,finally�� 在程�离开 try �都一定会被执行。当 try 语�中�生了未被 except �获的异常(或者它�生在 except 或 else ��中),在 finally ��执行完�它会被�新抛出。try 语��由 break,continue 或 return 语�退出也一样会执行 finally ��。以下是一个更��些的例�(在� 一个 try 语�中的 except 和 finally ��的工作方�与 Python 2.5 一样):

>>> 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
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

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

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

8.7. 预定义清�行为¶

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

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

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

with open("myfile.txt") as f:
    for line in f:
        print line

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