天天看點

系統學習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 
    ...      

​as​

​​在​

​try​

​​處理程式中是可選的(如果省略它,該執行個體則不會被指派給一個名稱),但是包含​

​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 2,in <module>
ZeroDivisionError: division by zero 

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

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

當在一個異常處理程式内部,由程式錯誤隐式地引發一個異常的時候,一個相似的過程會緊接着自動發生。前一個異常會添加到新異常的​

​__context__​

​屬性中,并且如果該異常未捕獲的話,則同樣會顯示在标準出錯消息中:

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

During handling of the above exception, another exception occurred:

Traceback(most recent call last):
File "<stdin>",line 4,in <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 3,in <module>
IndexError 

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

同理,下面的代碼會顯示三條異常,盡管它們都是隐式産生的:

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

與合并​

​try​

​語句一樣,連鎖引發的異常和其他語言中的用法相似(包括Java和C#),然而我們很難說這些語言之間到底是誰借鑒了誰。在Python中,這是一個進階的并且多少還有些難懂的擴充。事實上,Python3.3新增了種能夠阻止異常連鎖引發的方式:Python 3.3禁用連鎖異常:​

​raise from None​

​。Python 3.3引人了一種新的文法形式使用​

​None​

​作為​

​from​

​分句的異常名稱:

​raise newexception from None​