錯誤處理
程式執行到一半,有時會遇到沒辦法處理的狀況:使用者把數字欄位填成文字、除數是 0、要開的檔案不存在……這些叫做錯誤(也叫例外,exception)。一發生,程式預設就會當場停住、印出錯誤訊息。這一章看怎麼接住這些錯誤,讓程式繼續好好走下去。
try / except:接住錯誤
Section titled “try / except:接住錯誤”把可能出錯的程式放進 try,萬一真的出錯,就跳到 except 那段。except 後面寫的 Exception 是幾乎所有錯誤共同的上層,寫 except Exception: 就是「不管出什麼錯都接住」。下面這段做除法,除數填 0 本來會壞掉,但 except 把它接住了。按「執行」,除數輸入 0 試試:
try: a = int(input("被除數:")) b = int(input("除數:"))
print(a / b)except Exception: print("出錯了,這次沒算出來")重新執行,這次把被除數填成 abc:出的是另一種錯(文字轉不成數字),但一樣被同一個 except 接住。兩個欄位都填正常的數字,程式就走正常的路,except 那段完全不會碰到。它只在真的出錯時才會跑。
針對不同的錯誤
Section titled “針對不同的錯誤”什麼錯都接住,聽起來很安心,其實會出問題:程式裡真正的 bug 也會被一起吞掉,你只看到「出錯了」,卻不知道是哪裡錯。所以 except 後面通常會指定要接住哪一種錯誤。把上面的 Exception 換成 ZeroDivisionError,就只接「除以 0」這一種:
try: a = int(input("被除數:")) b = int(input("除數:"))
print(a / b)except ZeroDivisionError: print("不能除以 0!")除數輸入 0 一樣會被接住。重新執行,改把被除數填成 abc:int() 丟出的是 ValueError,不在這個 except 的範圍裡,程式就停下來、把錯誤原原本本報出來。指定種類就是為了這個:你只接住預期中的錯誤,其他沒想到的問題照樣會報錯,才不會把真正的 bug 藏起來。常見的錯誤還有 TypeError(型別用錯)、KeyError(字典沒有那個鍵)、IndexError(串列沒有那個位置)等等。
多個 except 區塊
Section titled “多個 except 區塊”同一段 try 可能出不只一種錯。前面的除法程式就有兩個地雷:除數填 0 會丟 ZeroDivisionError,兩個欄位任一個填成文字會丟 int() 的 ValueError。一個 try 後面可以接好幾個 except,每一個負責一種錯誤,Python 會由上往下找第一個對得上的區塊執行,其餘的都跳過。按「執行」,先照預設輸入 0 當除數:
try: a = int(input("被除數:")) b = int(input("除數:"))
print(a / b)except ZeroDivisionError: print("不能除以 0!")except ValueError: print("請輸入數字,不要輸入文字")這次走的是 ZeroDivisionError 那段。重新執行,把其中一個欄位改成 abc,就會改走 ValueError 那段。這樣每種錯誤都有自己的處理方式和提示訊息,比一個 except 包辦所有狀況清楚得多。
如果兩種錯誤要做的事一樣,也可以把它們用小括號包成一組,寫在同一個 except 後面:except (ZeroDivisionError, ValueError):。
錯誤有上下階層
Section titled “錯誤有上下階層”Python 內建的錯誤排成一棵樹:ZeroDivisionError 屬於 ArithmeticError(算術錯誤),KeyError 和 IndexError 都屬於 LookupError(查詢錯誤),而它們全都掛在前面用過的 Exception 底下。except 寫的是上層,就連底下的子類別一起接住:except LookupError: 同時接得到 KeyError 和 IndexError。
因此多個 except 的順序很重要:要把比較具體的錯誤放前面、比較籠統的放後面。如果先寫 except Exception:,後面的 except ValueError: 就永遠輪不到。
想知道有哪些內建錯誤、每一個代表什麼意思,可以查官方文件的內建的例外(英文頁面);同一頁最底下的例外階層把整棵樹畫了出來,寫 except 前不確定該接哪一個時,先去那裡對照。
else 與 finally
Section titled “else 與 finally”完整的結構還有兩段:
else:try裡沒有出錯時才執行。finally:不管有沒有出錯,最後一定會執行,常用來做收尾(例如關檔)。
try: number = int(input("輸入一個數字:"))except ValueError: print("那不是數字")else: print("成功讀到", number)finally: print("結束(這行一定會執行)")輸入 7 會走 else,再走 finally;如果輸入文字,就改走 except,一樣會走到 finally。
raise:主動丟出錯誤
Section titled “raise:主動丟出錯誤”除了接住錯誤,你也可以在該擋的地方主動丟出錯誤,用的是 raise。這常拿來擋不合理的輸入:
def set_age(age): if age < 0: raise ValueError("年齡不能是負的")
print("年齡設為", age)
set_age(25)set_age(-3)set_age(25) 正常印出,但 set_age(-3) 因為年齡是負的就 raise 了一個 ValueError。這裡沒有 try 去接它,所以程式就停下來、報出這個錯誤。這正是 raise 的用意:把不該發生的事情大聲講出來,而不是默默算下去。
v0.9.1 (c924b5b)