Python 程式設計

第 14 章    例外處理

∗ 例外 (Exception):

▸ 程式語言對於無法利用一般流程控制來處理的情況所發出的訊息

▸ 在 Python 裡,所有程式錯誤 (Error) 都是透過例外來處理,但並非所有例外都是程式錯誤

(1) 程式控制流程

∗ 程式執行之控制流程 (Flow-of-control) 有以下模式:

▸ 循序 (Sequential):一行指令接著一行指令執行

▸ 決策 (Decision):在判斷條件之後,流程控制不再循序,而是跳到某個程式區塊執行,例如 if-else 複合指令

▸ 迴圈 (Loop):在執行完迴圈的最後一個指令後,流程控制不再循序,而是跳到第一行迴圈判斷式,以決定是否重複執行,例如 forwhile 複合指令

▸ 函式呼叫 (Function invocation):到達函式呼叫指令時,流程控制不再循序,而是跳到函式的第一行指令,而在執行完函式最後一個指令後,再跳到呼叫函式的下一個指令

∗ 例外處理之控制流程

▸ 控制流程在某些狀況下運作效能不佳,例如以下範例:函式 main() 呼叫 A()A() 呼叫 B()B() 呼叫 C()C() 呼叫 D()

def main()
    A()

def A():
    B()

def B():
    C()

def C():
    D()

def D()
    # processing
✶ 假設 D()函式需回覆訊息給 main() 函式,唯一能做的就是依循控制流程將訊息回覆給 C() 函式、C() 函式再接力將訊息回覆給 B() 函式,以此類推最後傳到 main()
✶ 例外則提供另一管道,可以將訊息傳送給目前執行中而且會處理此訊息的程式,因此只要 main() 會處理 D() 所發的訊息,D() 就可以直接傳送訊息給 main(),這讓程式執行的效能大幅提昇
✶ 所有執行中的程式都依序放在執行堆疊 (Run-time stack) 裡,以上例而言,執行堆疊內容及順序如下:
main()
A()
B()
C()
D()

∗ Python 例外處理

▸ 發出例外訊息:

raise <exceptionName>

▸ 擷取例外訊息:

try:
    ...
except:    # try 部份若發生例外,都會處理
    ...

try:
    ...
except <exceptName>:    # try 部份若發生例外,僅會處理名為 <exceptName> 的例外
    ...

▸ 當程式發出例外訊息後,控制流程被打斷,隨即在執行堆疊中從底部往上尋找會處理此例外訊息的程式 (亦即有 try: except: 的程式),然後執行第一個被找到程式的 except: 部份

▸ 如果沒找到例外訊息處理程式,程式就會當掉 (Crash) 並顯示目前執行堆疊的內容

▸ 範例 1:

def main()
    A()

def A():
    B()

def B():
    C()

def C():
    D()

def D():
    try:
        # processing code
        if something_special_happened:
            raise MyException
    except MyException:
        # execute if the MyException message happened
✶ 當 raise MyException 執行時,流程控制在執行堆疊裡的 D() 函式找到 try: except: 區塊,結果是 D() 函式處理自己所發出的例外

▸ 範例 2:

def main()
    A()

def A():
    B()

def B():
    C()

def C():
    try: 
        D()
    except MyException:
        # Execute if the MyException message happened

def D():
    if something_special_happened:
        raise MyException
✶ 此處 C() 是處理例外的程式,如果 D() 發出例外,就會由 C() 來處理

▸ 範例 3:

def main()
    try: 
        A()
    except MyException:
        # Execute if the MyException message happened

def A():
    B()

def B():
    try:
        C()
    except ZeroDivisionError:
        # Execute if the ZeroDivisionError message happened

def C():
    D()

def D():
    if something_special_happened:
        raise MyException
✶ 此處 main() 是處理例外的程式,如果 D() 發出例外,就會由 main() 來處理
✶ 在 B() 中雖然也有 try: except: 區塊,但因為例外訊息名稱不同,因此不會處理

(2) 標準例外

∗ Python 有許多標準例外 (Standard exception),常見的如下:

語言例外說明
ImportError匯入錯誤
SyntaxError語法錯誤
IndentationError縮排錯誤
NameError識別字錯誤
NameError資料型態錯誤
IndexError索引錯誤
KeyError鍵值錯誤

數學例外說明
OverflowError超過最大值錯誤
ZeroDivisionError除以 0 錯誤
FloatingPointError浮點運算錯誤

I/O例外說明
FileNotFoundError找不到檔案錯誤
IOError輸出入錯誤
PermissionError權限錯誤

▸ 其餘例外請參考這裡

(3) 例外擷取

∗ 擷取所有例外

▸ 不給 <exceptName>,則任何例外都會處理

try:
    ...
except:
    ...

∗ 擷取特定例外

▸ 給 <exceptName>,就只會處理該項例外

try:
    ...
except <exceptName>:
    ...

∗ 擷取多個例外

▸ 串連多個 except,如果沒有例外發生,就執行 else 部份

try:
    ...
except <exceptName1>:
    ...
except <exceptName2>:
    ...
except <exceptName3>:
    ...
else:
    ... 

∗ 例外處理後執行清理 (Clean-up) 程式

▸ 如果不論例外是否發生,都需要執行某些程式,可以寫在 finally 部份 (註:try: 部份可能有些程式不會執行)

try:
    ...
finally:    # 最後一定會執行
    ...

∗ 例外處理的要領

It's better to ask for forgiveness than permission: 錯了才請求原諒比每次請求允許要好

✶ 如果某些指令執行成功的機率較高,就直接執行,不需要判斷該指令是否會成功才執行,如此不僅程式效能較好,而且會顯得較為簡潔
✶ 反之,如果指令執行成功的機率較低,可以先判斷,然後決定是否執行該指令

▸ 範例:讀取檔案裡的數字字串,並轉為整數

ch13/input.txt (有些資料並非整數)
10,20,30,40
aa,20,30,40
10,bb,30,40
10,20,cc,40
10,20,30,dd
10,20,30,40
10,20,30,40
10,20,30,40
10,20,30,40
10,20,30,40
✶ 請求允許 (Ask for permission):四次判斷,程式冗長
ch13/permission.txt
with open('input.txt', 'r') as infile:
    for line in infile:
        a, b, c, d = line.replace(' ', '').replace('\n', '').split(',')
        if not a.isnumeric():    # 如果不是數字,就跳過這行
            continue
        a = int(a)
        if not b.isnumeric():    # 如果不是數字,就跳過這行
            continue
        b = int(b)
        if not c.isnumeric():    # 如果不是數字,就跳過這行
            continue
        c = int(c)
        if not d.isnumeric():    # 如果不是數字,就跳過這行
            continue
        d = int(d)
        print(a, b, c, d)
10 20 30 40
10 20 30 40
10 20 30 40
10 20 30 40
10 20 30 40
10 20 30 40
✶ 請求原諒 (Ask for forgiveness):沒有判斷,程式簡潔
ch13/forgiveness.txt
with open('input.txt', 'r') as infile:
    for line in infile:
        try:
            a, b, c, d = line.replace(' ', '').replace('\n', '').split(',')
            a, b, c, d = int(a), int(b), int(c), int(d)
            print(a, b, c, d)
        except:    # 只要有一個無法轉為整數,就跳過這行
            continue
10 20 30 40
10 20 30 40
10 20 30 40
10 20 30 40
10 20 30 40
10 20 30 40

上一章       下一章