例外繼承架構
April 29, 2022在使用多個 except
時,必須留意例外繼承架構。
多個 except
如果例外在 except
的比對過程中,就符合了某個父型態,後續即使定義了 except
比對子型態例外,也等同於沒有定義。例如:
try:
dividend = int(input('輸入被除數:'))
divisor = int(input('輸入除數:'))
print(f'{dividend} / {divisor} = {dividend / divisor}')
except ArithmeticError:
print('運算錯誤')
except ZeroDivisionError:
print('除零錯誤')
執行上面這個程式片段,永遠不會看到 '除零錯誤'
的訊息,因為在例外繼承架構中,ArithmeticError
是 ZeroDivisionError
的父類別,發生 ZeroDivisionError
時,except
比對會先遇到 ArithmeticError
,就語義上 ZeroDivisionError
是一種 ArithmeticError
,因此就執行了對應的區塊,後續的 except
就不會再比對了。
在 Python 中,例外都是 BaseException
的子類別,當使用 except
沒有指定例外型態時,就是比對 BaseException
。例如:
while True:
try:
print('跑跑跑...')
except:
print('Shit happens!')
上面這個程式,無法透過 Ctrl+C 來中斷迴圈,因為只寫了 except
而沒有指定例外型態,這等同於比對了 BaseException
,也就是全部的例外都會比對成功,這包括了 KeyboardInterrupt
例外,執行過 except
區塊後,又仍在迴圈之中,因此永不停止。
然而,如果在 except
指定了 Exception
型態,就可以透過 Ctrl+C 中斷程式。例如:
while True:
try:
print('跑跑跑...')
except Exception:
print('Shit happens!')
KeyboardInterrupt
例外不是 Exception
的子類別,因此沒有對應的 except
可以處理 KeyboardInterrupt
例外,迴圈流程會被中斷,最後整個程式結束。
內建例外架構
Python 標準程式庫中,完整的例外繼承架構,可以在官方文件〈Built-in Exceptions〉中找到,基於查閱方便,底下直接列出。
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
先前談過,Python 中的例外並非都是錯誤,有時代表著一種通知,例如,StopIteration
只是通知迭代流程無法再進行了;方才看到的 KeyboardInterrupt
也是,表示發生了一個鍵盤中斷;SystemExit
是由 sys.exit
引發的例外,表示離開 Python 程式;GeneratorExit
會在產生器的 close
方法被呼叫時,從當時暫停的位置引發,如果在定義產生器時,想在 close
時為產生器做些資源善後等動作,就可以使用。例如:
>>> def natural():
... n = 0
... try:
... while True:
... n += 1
... yield n
... except GeneratorExit:
... print('GeneratorExit', n)
...
>>> n = natural()
>>> next(n)
1
>>> n.close()
GeneratorExit 1
>>>
SystemExit
、KeyboardInterrupt
、GeneratorExit
都直接繼承了 BaseException
,這是因為它們在 Python 中,都是屬於退出系統的例外,如果想自訂例外,不要直接繼承 BaseException
,而應該繼承 Exception
或 Exception
的相關子類別。
在繼承 Exception
自定義例外時,如果自定義了 __init__
,建議將自定義的 __init__
傳入的引數,透過 super().__init__(arg1, arg2, …)
來呼叫 Exception
的 __init__
,因為 Exception
的 __init__
預設接受所有傳入的引數,而這些被接受的全部引數,可透過 args
屬性取得。