系统学习Python——异常处理:raise语句

如果要显式地触发异常,可以使用raise语句。它们的一般形式相当简单。一条raise语句的组成包括raise关键字,后面跟着可选的要引发的异常类或者异常类的一个实例:

raise instance       #Raise instance of class
raise class          #Make and raise instance of class:makes an instance
raise                #Reraise the most recent exception

如前所述,从Python 2.6和Python 3.0以后异常总是类的实例。因此,这里第一种raise形式是最常见的。我们直接提供了一个实例,该实例要么是在raise之前创建的,要么就是raise语句中创建的。如果我们传入一个类,则Python会对这个类调用不带参数的默认构造函数,以此来创建被引发的一个异常实例;这个格式等同于在类引用后面添加圆括号。最后的一种形式会重新引发最近触发的异常;它通常用于异常处理程序中,用来传递已经捕获的异常。

Python 3.X中不再支持raise Exc,Args的形式,不过该形式在Python 2.X仍然可用。在Python 3.X中,需要使用我们介绍的raise实例构造调用形式。在Python 2.X中等价的逗号形式是一种语法的历史遗留物,目的是为了兼容现在已经废除的基于字符串的异常模型,这一模型在Python 2.X已经不复存在。如果被用到的话,这一形式将会被转换成Python 3.X的调用形式。

在早期的Python版本中,有一种raise Exc的形式也能用于命名一个类一—它在各个版本中都能被转换成raise ExC(),来调用类的无参数构造函数。除了被废除的逗号语法之外,Python 2.X的raise语句还能同时支持字符串和类异常。但是前者在Python 2.6中被移除并在2.5中被弃用,所以在我们不会详细介绍,现在我们需要使用新的基于类的异常。

引发异常

为了更清楚地说明,让我们看一些例子。对于内置异常,如下两种形式是等价的,两者都会引发指定的异常类的一个实例,但是第一种形式是隐式地创建实例:

raise IndexError         #Class(instance created)
raise IndexError()       #Instance(created in statement)

我们也可以提前创建实例,因为raise语句接受任何类型的对象引用,下面的例子像刚才一样引发了IndexError

exc = IndexError()                 #Create instance ahead of time 
raise exc 
excs = [IndexError,TypeError]
raise excs[o]

当引发一个异常的时候,Python把引发的实例与该异常一起发送。如果一个try包含了一个except name as X:形式的分句,那么raise中的异常实例就会赋值给变量X

try...
except IndexError as X:     #X assigned the raised instance object 
	...

astry处理程序中是可选的(如果省略它,该实例则不会被赋值给一个名称),但是包含as将使得处理程序能够访问异常实例中的数据以及异常类中的方法。

这种模型对于我们用类编写的用户定义的异常也同样有效。例如,下面的代码向异常类的构造函数中传入了参数,从而使该参数可以通过异常实例的赋值在处理程序中被使用:

class MyExc(Exception): 
    pass
...
raise MyExc('spam’)               #Exception class with constructor args 
...
try...
except MyExc as X:                             #Instance attributes available in handler 
    print(X.args)

不管你如何指定异常,异常总是通过实例对象来识别的,而且在程序运行的任意时刻,至多只能有一个处于激活状态的异常实例。一旦异常在程序中某处被一条except分句捕获,它就不会再传递给另一个try语句了,除非它被另一个raise语句或错误再次引发。

作用域和try except变量

我们将在后面的文章中深入学习异常对象。在Python 2.X中,一个except分句中的异常引用变量名不单单局限于分句本身,也就是说这一变量在except对应的代码块运行结束后,仍然可用。相反,Python 3.X会将异常引用名局限在对应的except块中——在块执行结束后该变量将不能再被使用,这和3.X中推导表达式的临时循环变量非常相似,还有前面提到的,Python 3.X不接受Python 2.X中的except分句逗号语法:

c:\code>py-3
>>> try...     1 / 0
... except Exception, X:
SyntaxError:invalid syntax

>>> try...     1 / 0
... except Exception as X:                  #3.X localizes las'names to except block
...     print(X)
division by zero
>>> X 
NameError:name'X"is not defined

然而不同于推导循环变量的是,这一变量在Python 3.X的except块退出后就被移除了。之所以这么做的原因在于如果不这么做,这一变量将继续持有Python运行时调用栈的一个引用,这会导致垃圾回收机制被推迟同时占用了额外的内存空间。即使你在别处使用了这一异常引用名,这种移除也将被强制执行,而且是一种比推导语法还要严格的策略:

>>> X=99
>>> try...        1 / 0
...    except Exception as X:                           #3.X localizes andremoves on exit!
...        print(x)
division by zero
>>> X 
NameError:name'X'is not defined

>>> X=99
>>> {
    
    X for X in'spam'}                                   #2.X/3.X localizes only:not removed
{
    
    's''a''p''m'}
>>>X
99

因此,你通常应当在try语句的except分句中使用独特的变量名,即便它们已经局限在这一作用域中。如果你确实需要在try语句后引用该异常实例,你只需将该实例赋值给另一个不会被自动移除的变量名即可:

>>> try...     1 / 0
... except Exception as X:                  #3.X localizes las'names to except block
...     print(X)
...     saveit = X
division by zero
>>> X 
NameError:name'X"is not defined
>>> saveit
ZeroDivisionError('division by zero')

利用raise传递异常

raise语句拥有比我们目前所看到的更加丰富的性质。例如,一个不带异常名称或额外数据值的`raise命令的作用是重新引发当前异常。一般如果需要捕获和处理一个异常,又不希望异常在程序代码中死掉时,就会采用这种形式。

>>> try:
...        raise IndexError('spam')       # Exceptions remember arguments
...    except IndexError:
...        print('propagating')
...        raise                         # Reraise most recent exception
propagating
IndexError  Traceback (most recent call last)
<ipython-input-1-5ce28f2f34b4> in <module>()
      1 try:
----> 2     raise IndexError('spam')      # Exceptions remember arguments
      3 except IndexError:
      4     print('propagating')
      5     raise                         # Reraise most recent exception

如果这样执行ra1se时,就能重新引发异常,并将其传递给更高层的处理程序(或者顶层的默认处理程序,它会停止程序并打印标准出错消息)。

Python 3.X异常链:raise from

异常有时能作为对其他异常的响应而被触发:它既可以是有意地被触发,也可以是由于其他新的程序错误而被触发。为了在这些情况中支持完全的开放性,Python 3.X也允许raise语句拥有一个可选的from分句:

raise newexception from otherexception

from分句被使用在一个显式raise请求中的时候,from后面跟的表达式指定了另一个异常类或实例,该异常(即上面的otherexception.)会附加到新引发异常(即上面的newexception)的__cause__属性。如果新引发的异常没有被捕获,那么Python会把两个异常都作为标准出错消息的一部分打印出来:

>>> try:
...    1 / 0
...except Exception as E:
...    raise TypeError('Bad!') from E                   # Explicitly chained exceptions 
Traceback (most recent call last):
    File "<stdin>",line 2in <module>
ZeroDivisionError: division by zero 

The above exception was the direct cause of the following exeeption:

Traceback(most recent call last):
    File "<stdin>",line 4in <module>
TypeError: Bad

当在一个异常处理程序内部,由程序错误隐式地引发一个异常的时候,一个相似的过程会紧接着自动发生。前一个异常会添加到新异常的__context__属性中,并且如果该异常未捕获的话,则同样会显示在标准出错消息中:

>>> try:
...        1 / 0
... except...      badname                      # Implicitly chained exceptions
Traceback(most recent call last):
File "<stdin>",line 2in <module>
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback(most recent call last):
File "<stdin>",line 4in <module>
NameError:name 'badname' is not defined

在上述两种情况中,因为原先异常在被添加到新异常对象的同时,自身可能也有添加的原因(更早的异常),所以异常的因果链可以为任意长度,并都会被完整地打印在错误信息中。也就是说,错误信息可以包含多于两条的异常。最终的效果是,在隐式和显式上下文中都能让程序员知道所有涉及的异常,只要一个异常触发了另一个:

>>> try:
...        try...            raise IndexError()
...        except Exception as E:
...            raise TypeErro() from E 
...    except Exception as E:
...        raise SyntaxError() from E
Traceback(most recent call last):
File "<stdin>",line 3in <module>
IndexError 

The above exception was the direct cause of the following exception:
    Traceback(most recent call last):
        File "<stdin>",line 5in <module>
    TypeError The above exception was the direct cause of the following exception:
    Traceback(most recent call last):
        File "<stdin>",line 7in <module>
SyntaxError:None

同理,下面的代码会显示三条异常,尽管它们都是隐式产生的:

>>> try...        try...            1 / 0
...        except...            badname
...    exceptopen('nonesuch')

与合并try语句一样,连锁引发的异常和其他语言中的用法相似(包括Java和C#),然而我们很难说这些语言之间到底是谁借鉴了谁。在Python中,这是一个高级的并且多少还有些难懂的扩展。事实上,Python3.3新增了种能够阻止异常连锁引发的方式:Python 3.3禁用连锁异常:raise from None。Python 3.3引人了一种新的语法形式使用None作为from分句的异常名称:
raise newexception from None

这条语句能禁用前文中提到的连锁异常上下文的显示。其效果是在实际处理异常连锁的过程中,显示更少的在异常类型间转换的错误信息。

猜你喜欢

转载自blog.csdn.net/hy592070616/article/details/124770886