Python錯誤處理和異常處理(二)

veelion發表於2019-03-07

前面我們講到Python程式設計過程中,在可能出現異常的地方使用try語句,來正確的處理一些異常,可以保證程式不中斷繼續執行。

錯誤和異常

丟擲異常

有時候,我們可能需要在程式的某些地方主動丟擲異常,通知呼叫該程式碼的程式有錯誤發生。這時候,我們就要用到raise語句。raise語句就是幫助我們丟擲知道異常的,比如:

In [6]: raise NameError("Bad Name")
-----------------------------------
NameError           Traceback (most recent call last)
<ipython-input-6-966a00c8f456> in <module>
----> 1 raise NameError("Bad Name")

NameError: Bad Name

raise的使用很簡單,它的語法如下:

raise [expression [from expression]]

如果它後面不帶表示式(引數),它會重新引發當前作用域內最後一個啟用的異常。 如果當前作用域內沒有啟用的異常,將會引發 RuntimeError 來提示錯誤。

如果後面帶有表示式,則將表示式求值為要丟擲的異常物件,該表示式必須是一個異常例項或者是一個異常類(繼承自BaseException類)。如果它是一個異常類,它將透過呼叫沒有引數的建構函式來隱式例項化:

raise NameError  # 等同於 'raise NameError()'

raise表示式後面還可以跟一個from子句,用於異常的串聯。from子句的表示式必須是另一個異常或例項,它將作為可寫的(writable)的__cause__屬性被關聯到所引發的異常。如果引發的異常未被捕捉處理,兩個異常都將被列印出來:

In [9]: try: 
   ...:     print(10/0) 
   ...: except Exception as e: 
   ...:     raise RuntimeError("something is wrong") from e 
   ...:
----------------------------------------------------------
ZeroDivisionError            Traceback (most recent call last)
<ipython-input-9-7de64aad634f> in <module>
      1 try:
----> 2     print(10/0)
      3 except Exception as e:

ZeroDivisionError: division by zero

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

RuntimeError                 Traceback (most recent call last)
<ipython-input-9-7de64aad634f> in <module>
      2     print(10/0)
      3 except Exception as e:
----> 4     raise RuntimeError("something is wrong") from e
      5 

RuntimeError: something is wrong

如果一個異常在except子句或finally子句中被丟擲,類似的機制會隱式地發揮作用,之前的異常將被關聯到新異常的__context__屬性。例如:

In [10]: try: 
    ...:     print(10/0) 
    ...: except: 
    ...:     raise RuntimeError("something is wrong") 
    ...:
-----------------------------------------------
ZeroDivisionError           Traceback (most recent call last)
<ipython-input-10-e950a6292482> in <module>
      1 try:
----> 2     print(10/0)
      3 except:

ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

RuntimeError                Traceback (most recent call last)
<ipython-input-10-e950a6292482> in <module>
      2     print(10/0)
      3 except:
----> 4     raise RuntimeError("something is wrong")
      5 

RuntimeError: something is wrong

異常串連可透過在from子句中用None來顯示地禁止:

In [11]: try: 
    ...:     print(10/0) 
    ...: except: 
    ...:     raise RuntimeError("something is wrong") from None 
    ...:
-------------------------------
RuntimeError                   Traceback (most recent call last)
<ipython-input-11-1818bd8b9d31> in <module>
      2     print(10/0)
      3 except:
----> 4     raise RuntimeError("something is wrong") from None
      5 

RuntimeError: something is wrong

使用者自定義異常

Python允許使用者自定義異常類,通常應該直接或間接地繼承自Exception類。

自定義的異常類的名稱通常以“Error”結尾,類似與內建標準異常的命名。自定義的異常類,可以像其它類那樣可以執行任何操作,但通常保持簡單,只提供用以處理程式為異常提取有關錯誤資訊的屬性。為模組自定義多個不同錯誤的異常時,通常是為該模組定義一個異常基類,再為不同錯誤建立特定的子類。例如:

class ModuleError(Exception):
    '''模組的異常基類'''
    pass


class ModuleNameError(ModuleError):
    '''模組的特定異常子類'''
    pass


class ModuleValueError(ModuleError):
    '''模組的另一個特定異常子類'''
    pass

最後的清理操作:finally 子句

finally子句是try語句的一個可選子句,用於定義在任何情況下都執行的操作,叫做“清理操作”。例如:

In [12]: try: 
    ...:     raise NameError 
    ...: finally: 
    ...:     print('Bye :)') 
    ...:      
    ...:
Bye :)
-------------------------------
NameError        Traceback (most recent call last)
<ipython-input-12-9cda1523ce81> in <module>
      1 try:
----> 2     raise NameError
      3 finally:
      4     print('Bye :)')
      5 

NameError: 

finally子句總會在離開try語句前被執行,無論發生異常與否。當在try子句中發生了異常且尚未被except子句處理(或者它發生在 except 或 else 子句中)時,該異常將在 finally 子句執行後被重新丟擲。 當 try 語句的任何其他子句透過 break, continue 或 return 語句離開時,finally 也會在“離開之前”被執行,參考下面這個更復雜的例子:

In [13]: def divide(a, b): 
    ...:     try: 
    ...:         result = a / b 
    ...:     except ZeroDivisionError: 
    ...:         print('divided by zero!') 
    ...:     else: 
    ...:         print('result is', result) 
    ...:     finally: 
    ...:         print('leaving try') 
    ...:

In [14]: divide(8, 2)
result is 4.0
leaving try

In [15]: divide(8, 0)
divided by zero!
leaving try

In [16]: divide('a', 2)
leaving try
-----------------------
TypeError              Traceback (most recent call last)
<ipython-input-16-324d9fa22da2> in <module>
----> 1 divide('a', 2)

<ipython-input-13-5e4380c62566> in divide(a, b)
      1 def divide(a, b):
      2     try:
----> 3         result = a / b
      4     except ZeroDivisionError:
      5         print('divided by zero!')

TypeError: unsupported operand type(s) for /: 'str' and 'int'

從上面的例子我們看到,finally子句總是會被執行。但字串被除時引發了TypeError的異常,這個異常沒有被except子句處理,就會在finally子句執行後被重新丟擲。

在程式設計實踐中,finally子句對釋放檔案或網路連線等外部資源是非常有用的。

總結

程式設計中,我們不僅要在恰當的地方處理異常,也要在必要的時候丟擲異常,我們丟擲異常時可以自定義異常。熟練運用異常可以使我們的程式更加健壯,別忘了必要的時候使用finally來釋放外部資源。

猿人學banner宣傳圖

我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。

***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***

相關文章