例外處理

      在〈例外處理〉中尚無留言

程式撰寫中, 一定會有一些錯誤, 這些bug除了造成運算結果的不正確, 嚴重的還會引發程式閃退. 比如不小心除以0了. 除以0還是個小事. 但如果將資料寫到硬碟, 寫了一半, 磁碟滿了, 傳送到網路傳到一半斷線了, 那該怎麼辦! 直接閃退死給他看, 還是說偵測到錯誤, 發送解決的請求, 然後再試一次?

白痴也知道會是第二種. 問題是如何去偵測這種不知什麼時候會發作的錯誤呢

try-except就是當今的濟世良方. 請先看一下如下代碼

try:
    a=10
    b=0
    c=a/b
    print("c=%d" %c)
    exit()
except ZeroDivisionError:
    print("除到0了啦")
    exit()
finally:
    print("這裏是finally")

上面因為除到0, 所以就會跳到 except中, 列出錯誤訊息後, 執行exit()結束掉. 但很奇怪的是, 都結束了, 為什麼還會印出finally區塊裏面的訊息呢. 這是因為finally非常的頑強, 不論有沒有發生exception, 都會執行finally, 甚至是exit(), 要死, 也要執行完finally才會死

語法

完整語法如下, 比Java多了一個else的區塊

try:
    pass
except ZeroDivisionError:
    print("zero")
except ValueError:
    print("vaule")
else:
    print("else")
finally:
    print("finally")

Except類別

Python 的例外類別都是繼承了BaseException. 在多個except時, 最前面必需是子類別, 後面才是父類別. 因為父類別的範圍較大, 如果寫在前面的話, 會把所有例外都攔截下來, 那麼後面的子類別就無英雄用武之地了.

Call Stack

大陸翻成調用棧. 不過從這個名詞去看, 不太容易理解, 先以下面的例子作說明.

在math函數中, 發生除以0的錯誤, 但在math並沒有處理, 所以錯誤會丟回上一層的函數. 每一層函數都是一個stack, 所以這種把錯誤交給上一層函數去處理的方式, 稱為Call Stack.

到最上層如果也沒處理的話, 就丟給系統處理, 然後就閃退GG了.

def math(x, y):
    return x/y

def main():
    try:
        r=math(10,0)
    except ZeroDivisionError:
        print("Divide by zero")
    except ValueError:
        print("VauleError")
    else:
        print("No Error")
    finally:
        print("This is finally")
if __name__=="__main__":
    main()

log

print(e) 只會印出簡單的說明, 如division by zero. 而logging.exception(e)可以把整個stack的錯誤全都印出來

except ZeroDivisionError as e:
    print(e)
    logging.exception(e)
結果 : 
直接印 : division by zero
ERROR:root:division by zero
This is finally
Traceback (most recent call last):
 File "D:/python/oop1.py", line 7, in main
 r=math(10,0)
 File "D:/python/oop1.py", line 3, in math
 return x/y
ZeroDivisionError: division by zero

自訂例外

如果要定義自訂例外, 則可以撰寫class, 並繼承相關的例外, 再使用raise 將例外丟出, 如下範例

import logging
class MathError(ValueError):
    pass
def math(x, y):
    if y==0:
        raise MathError("被除數不能為0啦")
    return x/y

def main():
    try:
        r=math(10,0)
    except ValueError as e:
        print(e)
if __name__=="__main__":
    main()

重丟

下面代碼中, 抓到例外後, 可以再繼續丟給上一層, 只要寫raise即可, 不需註明要任的例外, 就會把本身的例外往上丟. 當然也可以寫其他的例外, 轉化成其他的例外物件.

def main():
    try:
        r=math(10,0)
    except ValueError as e:
        print(e)
        raise

print

寫程式時, 通常會先驗証邏輯計算是否正確. 比如要將資料寫入資料庫, 當然不會在寫完資料收集的程式碼後, 就馬上接著寫連線資料庫的程式. 總得要先列印出來看看抓到的資料是否正確嘛, 不然寫入資料庫的資料都是錯了, 資料庫不就整個毀了.

列印資料, 當然就是用print()印到螢幕上檢查囉. 另外某個變數, 計算後的結果是不是符合我們的期待, 也通常會先印出來看看.

print()好用歸好用, 但當測試完後, 就要手動刪除, 不然留著一直列印, 是很消耗CPU的資源的.

Assert

中文是斷言的意思. 這好比是在發誓一樣~~我發誓我一定會變有錢人, 不然你就給它爛掉.

assert n!=0, ‘除到0了啦’

前面的 n!=0就是在發誓, 後面的字串就是沒有達成的後果

當前面的保証沒達成, 就會發送AssertionError的例外, 此例外的訊息就是後面的那串字串.

def math(x, y):
    assert y!=0, "除到0了啦"
    return x/y
def main():
    try:
        r=math(10,0)
    except AssertionError as e:
        print(e)
if __name__=="__main__":
    main()
結果 : 
除到0了啦

當然一大堆assert也是很煩的, 所以可以在執行程式時, 下達大寫的O關閉 assert的動作

python -O myapp.py

logging

logging.info(‘字串’) 是用來取代print()的, 它就是直接把字串印出來而以. 但可以利用level等級來控制何時要列印. Logging才是程式debug的終極武器.

import logging
logging.basicConfig(level=logging.INFO)
def math(x, y):
    logging.info('y的值為 %d' % y)
    return x/y
def main():
    try:
        r=math(10,0)
    except ZeroDivisionError:
        pass
if __name__=="__main__":
    main()
結果 : 
INFO:root:y的值為 0

level共有四個等級, debug, info, warning, error. Debug 等級最低, 無論什麼狀況都會執行, error最高, 只有發生錯誤時才會執行.

pdb

pdb是python的單行執行除錯器, 可以一步一步的執行

python -m pdb myapp.py

進入互動模式後可以執如下指令

n : 下一步
l : 是小寫的L, list的意思, 把程式碼印出,並會有箭頭指向目前執行的位置
p 變數名 : 列出變數的值
q : 結束單行偵測

(Pdb) l
 7 try:
 8 r=math(10,0)
 9 except ZeroDivisionError:
 10 pass
 11 if __name__=="__main__":
 12 -> main()
[EOF]

pdb.set_trace()

pdb.set_trace()是設定一個中斷點, 也就是執行到此處就停止, 進入pdb互動模式, 同樣也是 p 變數 可列印變數直, n是下一步, c 繼續執行.

import logging
import pdb
logging.basicConfig(level=logging.WARNING)
def math(x, y):
    logging.info('y的值為 %d' % y)
    pdb.set_trace()
    return x/y
def main():
    try:
        r=math(10,0)
    except ZeroDivisionError:
        pass
if __name__=="__main__":
    main()

IDE設定中斷點

在行數旁按一下, 會出現紅色的點, 右按鍵選擇”All”, 然後從 Run/debug xxx.py即可

python_breakpoint

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *