iT邦幫忙

1

【python的異常處理】異常的捕捉(try...except...else...finally)與異常拋出(raise)的詳盡解說

今天來分享一個程式觀念: 異常處理
大家寫程式應該常有這樣的經驗吧,
就是程式的語法都正確,
程式編譯也可以通過,
但是在執行期間會發生錯誤

譬如說這樣一支簡單的程式:

print(1/2)
print(1/0)
print(1/4)

看一下結果印出了什麼:

0.5
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print(1/0)
ZeroDivisionError: division by zero

程式在執行第一行的時候很正常,印出0.5
執行到第二行的時候便出錯了,顯示ZeroDivisionError
因為除法除以0是無意義的

由於程式在第二行出錯了,
第三行的print(1/4)便執行不到了

try...except

異常處理就是說程式去捕抓程式執行期間的錯誤,
讓程式能夠繼續執行,
基礎的語法為

try:
    # 嘗試執行一些程式碼
except:
    # 當程式出現異常時執行這邊的程式碼

例如:

try:
    print(1/0)
except:
    print("除數不可以為0")

結果印出
除數不可以為0

使用try...except可以讓程式正常執行而不會中斷

觀念: 為什麼要用異常處理?

我其實覺得try...except的語法不會太難,
在網路上搜索應該可以查到蠻多很好的資料(我附於「參考資料中」),
也有詳細的語法解說,
所以這邊要來聊聊為什麼要用「異常處理」?

不知道大家會不會有跟小馬一樣的想法?
程式出錯就讓它錯啊,
就是要程式出錯讓程式停下來自己才知道程式錯了嘛,
為什麼要補抓錯誤讓程式繼續跑呢?

我說說我的看法:
小馬認為,會需要異常處理的情境在於,
寫一支能夠跟使用者互動的程式時會需要,
有時候,使用者的行為不一定跟你預期的相同,
這時,如果因為使用者的錯誤使用,
整支程式就當掉了,
那麼使用者體驗可能就不佳,
異常處理在於幫使用者考慮程式可能會出錯的地方,
並做一些例外的處理

也就是說,如果你只是想寫一支程式給自己使用,
那的確可能沒什麼必要做異常處理
因為當你執行程式出錯它就自己跳出錯誤訊息了

情境: 簡易除法計算器

(程式於線上程式編輯器repl.it中測試)
例如說你今天開發了一台「除法計算器」,
程式很簡單,
你想要讓別人可以輸入兩行數字,
然後可以計算兩數相除的結果,程式如下:

while True:
    print("請輸入二個正整數(每個數一行),程式將為您算出兩數相除的結果")
    x = int(input())
    y = int(input())
    print(x/y)

使用範例:
https://ithelp.ithome.com.tw/upload/images/20200530/20117114AsD9jZI136.png

寫完之後,你感到非常的滿意,
如果不是計算機已經被發明了,搞不好這支程式還能賣錢呢~
問題是: 你覺得使用者真的會乖乖輸入二個整數嗎?

大家以前多少會有這種調皮搗蛋的經驗,
玩一款遊戲時,遊戲指示怎麼說,
偏不照著做,看看會發生什麼事
譬如說玩賽車類的遊戲,
終點在前方,我偏要逆向行駛看看

於是不知道誰家的牛孩子,
看了你的計算器,
給你輸入
1
0

結果…程式掛了(除數不能為0),
要繼續用只能再重開,
使用者就想說「你這程式怎麼這麼爛」,紛紛想要退貨。
(這感覺大概像你玩賽車遊戲,嘗試逆向行駛,
然後整個遊戲就掛了一樣)

if-else能解決問題嗎?

不過你還是不相信你真的需要學try...except語法,
你想說好吧,除數不能為0,
寫了if-else判斷阻止牛孩子玩壞程式總行了吧?
你開始寫你的「除法計算器2.0版」:

print("歡迎使用「野馬除法計算器2.0版」……")
while True:
    print("請輸入二個正整數(每個數一行),程式將為您算出兩數相除的結果")
    x = int(input())
    y = int(input())
    if y!=0:
        print(x/y)
    else:
        print("除數不可以為0")

嗯…頓時你又感到非常的滿意,這下總不會被牛孩子玩壞了吧?

但是,你不知道牛孩子的創造力超乎你的想像,
這天,牛孩子輸入一個字
a
https://ithelp.ithome.com.tw/upload/images/20200530/20117114m44almMwV8.png

然後程式又掛了,
估計你大概有點想捶他,
使用說明上寫著請輸入數字,
你居然輸入英文字母?!

於是你繼續修改你的程式,
不過大概你懶的學新語法try...except,
傲驕的說道:「不想用不想用就是不想用。」
想說再多寫幾個if-else好了,
這次改成先判斷讀進來的字串,如果確定是數字才做int型態轉換

print("歡迎使用「野馬除法計算器2.1版」……")
while True:
    print("請輸入二個正整數(每個數一行),程式將為您算出兩數相除的結果")
    in_x = input()
    in_y = input()
    if in_x.isdigit() and in_y.isdigit():
        x = int(in_x)
        y = int(in_y)
    else:
        print("請不要輸入數字以外的字元")
        continue
    if y!=0:
        print(x/y)
    else:
        print("除數不可以為0")

這下牛孩子不能輸入
a
b
讓程式當掉了吧,不過…

https://ithelp.ithome.com.tw/upload/images/20200530/20117114W8sIQYFnTQ.png

後來客服電話還是被打爆,
使用者反映說你這計算器有問題,
我就正常的輸入
5
7
怎就不給算哈?然後附上截圖之後,不忘給個差評。
經過研究一番之後,你終於發現,原來他輸入數字5的時候,
後面不小心多打了一個空白,
空白被認為不是數字字元。

總之呢~ 為了處理一些不可預期的使用者行為,
我們已經把程式邏輯弄的太複雜了,
而且程式出錯的原因很多,
很難設想的很全面、周到,
因此就需要用異常處理的方式來捕捉錯誤了

使用try...except的方式修改如下:

print("歡迎使用「野馬除法計算器3.0版」……")
while True:
    print("請輸入二個正整數(每個數一行),程式將為您算出兩數相除的結果")
    try:
        x = int(input())
        y = int(input())
        print(x/y)
    except ZeroDivisionError:
        print("除數不可以為0")
    except ValueError:
        print("輸入格式有誤")
    except:
        print("程式出現其它異常")

這下程式的邏輯非常簡單,
你可以用except指定捕捉哪一種錯誤(不寫的話就是捕捉任何型態的錯誤),
譬如說ZeroDivisionError就是「除數為0」的錯誤,
ValueError是轉換為int時出錯,
最後用個except捕捉任何型態的錯誤,
避免程式掛掉(再也不怕牛孩子把程式玩壞了/images/emoticon/emoticon42.gif)

語法介紹- try...except...else...finally

除了較常用的try...except語法,
完整可以寫try...except...else...finally(不過似乎較不常用),
就當是學習新知吧~

在python 中,
try...except 可以和else, finally搭配使用,
else 是如果例外沒有發生會執行的程式,
finally是無論例外有沒有發生都會執行的程式。

例如這支程式可以先讀進來一個字串,
如果它可以轉換成數字的話,印出此字串,
否則印出錯誤訊息。
範例:

S = input("輸入一個數字").strip()
try:     
    print(int(S))
except Exception as e: 
    print(e)
else:
    print("Good String")
finally:
    print("Final")

讀者可以嘗試執行這支程式,
看看輸入不同的字串會發生什麼事。

觀念: 手動拋出異常

剛剛示範的try...except都是捕捉程式內建的異常,
那能不能自己定義異常呢?
比如說你在網站上註冊帳號,它可能會要求說密碼長度要介於8~16個字元之間(包含),
如果密碼長度小於8或大於16就是一個異常(僅管程式語法都沒問題)

python中,使用raise可以手動設定一個異常
範例:

try:
    pwd = input("請輸入您的密碼: ")
    if len(pwd)<8:
        raise Exception("密碼長度不足")
    if len(pwd)>16:
        raise Exception("密碼長度太長")
except Exception as e:
    print("密碼長度檢查異常: " + e)    

使用者範例:

請輸入您的密碼: abc123
密碼長度檢查異常:  密碼長度不足

參考資料

什麼是異常處理,Python常見異常類型(入門必讀)
Python try except異常處理詳解(入門必讀)
Python raise用法(超級詳細,看了無師自通)


尚未有邦友留言

立即登入留言