Skip to content

6. 异常

接下来我们将探讨异常。

  

程序 (exceptions_01)

第一个脚本说明了处理异常的必要性。

1
2
3
4
#   -*- coding=utf-8 -*-

#  we cause an error
x=4/0

我们故意引发错误,以查看解释器生成的信息。输出结果如下:

1
2
3
4
Traceback (most recent call last):
  File "D:\data\istia-1112\python\tutoriel\exceptions_01.py", line 4, in <module>
    x=4/0
ZeroDivisionError: integer division or modulo by zero

第 4 行显示:

  • 异常类型:ZeroDivisionError;
  • 相关的错误信息:整数除以零或模运算结果为零。该信息为英文。您可能需要修改这一点。

编程的基本原则是,我们必须尽一切可能避免像上面那样的“失控”崩溃。即使发生错误,程序也必须通过提供有关所发生错误的信息来正常终止。

异常处理的语法如下:


try:
    actions susceptibles de lancer une exception
except [classe d'exception, ...]:
    actions de gestion de l'exception
finally:
   actions toujours exécutées qu'il y ait exception ou non

try 代码块中,一旦发生异常,操作的执行就会立即停止。此时,执行将转至 except 子句中的操作。


except [MyException, ...]:

会拦截类型为 MyException 或其派生类的异常。当 try 块中发生异常时,解释器会按编写顺序检查与该 try 块关联的 except 子句。它会在第一个能够处理所发生异常的 except 子句处停止。如果找不到,异常将回传至调用方法。 如果该方法包含 try/except 代码块,则再次处理该异常;否则,异常将继续向上沿调用方法链传播。最终,异常将到达 Python 解释器。此时解释器会终止正在运行的程序,并显示如前例所示类型的错误信息。因此,规则是主程序必须捕获所有可能从被调用方法传播过来的异常。

异常携带有关已发生错误的信息。可以使用以下语法检索这些信息:


except MyException as informations:

information 是一个元组,其中包含与该异常相关的信息。

语法


except MyException, erreur:

将与该异常相关的错误消息赋值给变量 error

要抛出异常,请使用以下语法


raise MyException(param1, param2, ...)

其中 MyException 通常是继承自 Exception 类的子类。传递给该类构造函数的参数将在异常处理结构的 except 子句中可用。

以下脚本演示了这些概念。


程序 (exceptions_02)

以下脚本显式处理了错误:

#   -*- coding=utf-8 -*-

i=0
#  cause an error and manage it
x=2
try:
    x=4/0
except ZeroDivisionError, erreur:
    print ("%s : %s ") % (i, erreur)
#  the value of x has not changed
print "x=%s" % (x)

#   here we go again
i+=1
try:
    x=4/0
except Exception, erreur:
    #  we intercept the most general exception
    print ("%s : %s ") % (i, erreur)

#   several types of exceptions can be intercepted 
i+=1
try:
    x=4/0
except ValueError, erreur:
    #  this exception does not occur here
    print ("%s : %s ") % (i, erreur)
except Exception, erreur:
    #  we intercept the most general exception
    print ("%s : (Exception) %s ") % (i, erreur)
except ZeroDivisionError, erreur:
    #   we intercept a specific type
    print ("%s : (ZeroDivisionError) %s ") % (i, erreur)

#  start again, changing the order 
i+=1
try:
    x=4/0
except ValueError, erreur:
    #  this exception does not occur here
    print ("%s : %s ") % (i, erreur)
except ZeroDivisionError, erreur:
    #   we intercept a specific type
    print ("%s : (ZeroDivisionError) %s ") % (i, erreur)
except Exception, erreur:
    #  we intercept the most general exception
    print ("%s : (Exception) %s ") % (i, erreur)

#  an except clause with no arguments
i+=1
try:
    x=4/0
except:
    #  we're not interested in the nature of the exception or the error msg
    print ("%s : il y a eu un probleme ") % (i)

#   another type of exception
i+=1
try:
    x=int("x")
except ValueError, erreur:
    print ("%s : %s ") % (i, erreur)

#  an exception transports information in a tuple accessible to the program
i+=1
try:
    x=int("x")
except ValueError as infos:
    print ("%s : %s ") % (i, infos)

#   exceptions can be thrown
i+=1
try:
    raise ValueError("param1","param2","param3")
except ValueError as infos:
    print ("%s : %s ") % (i, infos)

#   you can create your own exceptions
class MyError(Exception):
    pass

#   throw MyError exception
i+=1
try:
    raise MyError("info1","info2", "info3")
except MyError as infos:
    print ("%s : %s ") % (i, infos)

#   throw MyError exception
i+=1
try:
    raise MyError("mon msg d'erreur")
except MyError, erreur:
    print ("%s : %s ") % (i, erreur)

#   any type of object can be launched
class Objet:
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg

i+=1
try:
    raise Objet("pb...")
except Objet as erreur:
    print "%s : %s" % (i, erreur)

#  the finally clause is always executed
#  whether or not there is an exception
i+=1
x=None
try:
    x=1
except:
    print "%s : exception" % (i)
finally:
    print "%s : finally x=%s" % (i,x)

i+=1
x=None
try:
    x=2/0
except:
    print "%s : exception" % (i)
finally:
    print "%s : finally x=%s" % (i,x)

注:

  • 第 6–9 行:处理除以零的情况;
  • 第 8 行:捕获实际发生的异常;
  • 第 11 行:由于发生了异常,x 未获得值,因此未发生变化;
  • 第 15–19 行:我们执行相同操作,但捕获类型为 Exception 的更高层级异常。由于 ZeroDivisionError 异常继承自 Exception 类,因此 except 子句会捕获它;
  • 第 23–33 行:我们使用多个 except 子句来处理多种异常类型。仅会执行其中一个 except 子句,若异常与任何 except 子句均不匹配,则不会执行任何子句
  • 第 51–55 行:except 子句可以不带参数。在这种情况下,它将捕获所有异常;
  • 第 59–62 行:引入 ValueError 异常
  • 第 66–69 行:获取异常携带的信息;
  • 第 73–76 行:介绍如何抛出异常;
  • 第 79–84 行:演示自定义异常类 MyError 的用法
  • 第 79–80 行:MyError 类仅继承自基类 Exception。它并未向基类添加任何内容。但现在,它可以在 except 子句中被显式指定
  • 第 97–107 行:演示在 Python 中,实际上可以抛出任何类型的对象,而不仅仅是继承自 Exception 类的对象
  • 第 109–127 行:演示 finally 子句的使用。

屏幕输出如下:

0 : integer division or modulo by zero
x=2
1 : integer division or modulo by zero
2 : (Exception) integer division or modulo by zero
3 : (ZeroDivisionError) integer division or modulo by zero
4 : il y a eu un probleme
5 : invalid literal for int() with base 10: 'x'
6 : invalid literal for int() with base 10: 'x'
7 : ('param1', 'param2', 'param3')
8 : ('info1', 'info2', 'info3')
9 : mon msg d'erreur
10 : pb...
11 : finally x=1
12 : exception
12 : finally x=None

程序 (exceptions_03)

这个新脚本演示了异常如何沿调用方法链向上传播:

#    -*- coding=utf-8 -*-

#    a proprietary exception
class MyError(Exception):
    pass

#    three methods
def f1(x):
    #  exceptions are not handled - they are automatically logged
    return f2(x)

def f2(y):
    #  exceptions are not handled - they are automatically logged
    return f3(y)

def f3(z):
    if (z % 2) == 0:
        #  if z is even, throw an exception 
        raise MyError("exception dans f3")
    else:
        return 2*z

#   ---------- hand

#  exceptions go up the chain of methods called
#  until a method intercepts it. In this case, it will be main
try:
    print f1(4)
except MyError as infos:
    print "type :%s, arguments : %s" % (type(infos),infos)

#  a method can enrich the exceptions it raises
def f4(x):
    try:
        return f5(x)
    except MyError as infos:
        #  we enrich the exception then relaunch it
        raise MyError(infos,"exception dans f4")

def f5(y):
    try:
        return f6(y)
    except MyError as infos:
        #  we enrich the exception then relaunch it
        raise MyError(infos, "exception dans f5")

def f6(z):
    if (z % 2) == 0:
        #    throw an exception
        raise MyError("exception dans f6")
    else:
        return 2*z

#   ---------- hand
try:
    print f4(4)
except MyError as infos:
    print "type :%s, arguments : %s" % (type(infos),infos)

注:

  • 第 27–30 行:在调用 main → f1 → f2 → f3(第 28 行)的过程中,由 f3 抛出的 MyError 异常将向上传播至 main。随后,该异常将由第 29 行的 except 子句进行处理;
  • 第 55–58 行:在调用 main → f4 → f5 → f6(第 56 行)的过程中,由 f6 抛出的 MyError 异常将向上传播至 main。随后,该异常将由第 29 行的 except 子句进行处理。这一次,随着异常沿调用方法链向上传播,MyError 异常会被沿途每个方法添加的信息所丰富。

屏幕输出如下:

type :<class '__main__.MyError'>, arguments : exception dans f3
type :<class '__main__.MyError'>, arguments : (MyError(MyError('exception dans f6',), 'exception dans f5'), 'exception dans f4')