except -- 例外處理



在程式設計上,常見的錯誤可分為兩類,一類是「語法錯誤」,一類是「例外錯誤」。

語法錯誤
 
# if 最後面少了冒號
if x>100
    print('x=', x)
 


語法錯誤較容易發現,因為只要程式一執行,Python 直譯器就會指出錯誤的地方了
 
  File "practice.py", line 2
    if x>100
           ^

SyntaxError: invalid syntax
 


例外錯誤就比較麻煩了,大多數的情況程式都可以正常執行,但就會因某個特殊情況導致程式當掉。

程式一當掉,發生錯誤的地方之後的程式碼也就不會執行了。例如:
 
# 只要使用者不輸入 0
# 程式會無止盡的執行下去

while True:
    x = int(input('請輸入任意數字'))
    print('100 /', x, '=',100/x)



# 但是,一旦使用者輸入 0
# 就爆出錯誤訊息了

Traceback (most recent call last):
  File "practice.py", line 4, in <module>
    print('100 /', x, '=',100/x)
ZeroDivisionError: division by zero
 

所以,要避免預期外的未爆燀,就要以「例外處理」預防

例外處理的語法:
 
try:
    指令 1
    指令 2
    ..
    ..
except 例外類別一:
    # 當預期外的錯誤發生時
    # 要做的補救措施
    指令 1
    指令 2
    ...
    ...
except 例外類別二:
    # 當預期外的錯誤發生時
    # 要做的補救措施
    指令 1
    指令 2
    ...
    ...
else:
    # 當沒有例外錯誤發生時會執行本段指令
    指令 1
    指令 2
    ...
    ...
finally:
    # 無論有沒有發生錯誤, 都會執行
    指令 1
    指令 2
    ...
    ...
 


前面的範例加上例外處理機制:
 
try:
    while True:
        x = int(input('請輸入任意數字'))
        print('100 /', x, '=',100/x)
except ZeroDivisionError:    # 遇到分母為 0 的意外類別時要做的處置
    # 一旦程式進到這裡
    # 表示已離開迴圈, 不會再無止盡的執行
    print('分母不可以是 0')
 
這樣子,遇到使用者輸入 0 時,就會提示不可輸入 0,而不會爆出錯誤了,不過程式也因此停止了。


將程式的順序調整一下
 
while True:
    try:
        x = int(input('請輸入任意數字'))
        print('100 /', x, '=',100/x)
    except ZeroDivisionError:
        print('分母不可以是 0')
 
就可以像原本一樣,即使使用者輸入 0,會提示使用者不可輸入 0,並繼續讓使用者輸入。



一般的情境會是這樣,只要沒有遇到例外,就一路往下執行;當例外發生時,就以 except: 內的程式處理、排除,處理完後,回到原本要正常執行的地方重來一遍,一旦結果執行正常,就繼續往下走。

所以正常情況下,while 迴圈只會執行一次
 
while True:
    try:
        x = int(input('請輸入任意數字: '))
        print('100 /', x, '=',100/x)
        break
    except ZeroDivisionError:
        print('分母不可以是 0')
print('程式沒爆錯誤訊息, 繼續往下執行')
 


except 則是在 try 語法中,當程式正常執行,沒有發生例外錯誤時,要繼續執行的程式區段;所以 else 必須放在 except 之後。

上面的範例,我們改將 break 放到 else 區段
 
while True:
    try:
        x = int(input('請輸入任意數字: '))
        print('100 /', x, '=',100/x)
    except ZeroDivisionError:
        print('分母不可以是 0')
    else:
        break
print('程式沒爆錯誤訊息, 繼續往下執行')
 
執行結果
 
請輸入任意數字: 9
100 / 9 = 11.11111111111111
程式沒爆錯誤訊息, 繼續往下執行
 


除了 ZeroDivisionError 外,還有 NameErrorTypeError、...等,這些稱為 Python內建的例外錯誤」,見下表
 
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
 
有些例外類別還可細分出子類別,例如 ZeroDivisionErrorFloatingPointErrorOverflowError 皆屬於 ArithmeticError 的子類別


除了輸入 0,還要防止使用者輸入不是數字的情況
 
while True:
    try:
        x = int(input('請輸入任意數字'))
        print('100 /', x, '=',100/x)
    except ZeroDivisionError:
        print('分母不可以是 0')
    except ValueError:
        print('您輸入的不是數字')
 



except 後面不加參數,則所有會引起例外的錯誤都會被觸發,所以這種用法要小心
 
# 注意這裡引入了系統函式模組
# 並移除了 while 迴圈

import sys

try:
    x = int(input('請輸入任意數字'))
    print('100 /', x, '=',100/x)
except ZeroDivisionError:
    print('分母不可以是 0')
except ValueError:
    print('您輸入的不是數字')
except:
    # 顯示觸發錯誤的類別
    print('例外錯誤:', sys.exc_info()[0])
 
執行結果
 
# 當按下 Ctrl + C 時, 也會觸發例外

請輸入任意數字例外錯誤: <class 'KeyboardInterrupt'>
 



從上表知道,Exception 底下有最多的子例外類別,所以您可以在最後以 Exception 捕捉其餘會發生的例外錯誤
 
try:
    x = int(input('請輸入任意數字'))
    print('100 /', x, '=',100/x)
except ZeroDivisionError:
    print('分母不可以是 0')
except ValueError:
    print('您輸入的不是數字')
except Exception as e:
    print('例外錯誤,', '類別:', type(e), '說明:', str(e))
 



最後是 finally,必須在整個 try 語法的最末,無論有沒有例外錯誤發生,finally 區段內的指令都會執行
 
while True:
    try:
        x = int(input('請輸入任意數字: '))
        print('100 /', x, '=',100/x)
    except ZeroDivisionError:
        print('分母不可以是 0')
    except ValueError:
        print('您輸入的不是數字')
    except Exception as e:
        print('例外錯誤,', '類別:', type(e), '說明:', str(e))
    else:
        break
    finally:
        print('不管有沒有例外錯誤發生,我都會出現')
print('程式沒爆錯誤訊息, 繼續往下執行')
 
執行結果:
 
# 正常執行, 沒有例外錯誤發生

請輸入任意數字: 15
100 / 15 = 6.666666666666667
不管有沒有例外錯誤發生,我都會出現
程式沒爆錯誤訊息, 繼續往下執行



# 輸入 n, 觸發例外錯誤

請輸入任意數字: n
您輸入的不是數字
不管有沒有例外錯誤發生,我都會出現
請輸入任意數字:
 


在開發程式的過程中,我們可以主動以 raise 指令觸發例外錯誤,以驗證 except 區段的程式執行結果是否真照我們預期。
 
try:
    raise ValueError
except ValueError:
    print('您輸入的不是數字')
 
雖然 raise 後可以不接任何東東,不過習慣上,我們會接上要觸發的例外類別。





⇑ 目錄 ⇑