Python之異常處理
- 2019 年 11 月 6 日
- 筆記
1
Python中的錯誤處理
在程式運行的過程中,難免會出現這樣那樣的錯誤,有些錯誤是我們自己程式編寫上有問題,也就是程式設計師聽了會砍人的那句話,"喲,寫bug呢!",還有一種是無法預測的錯誤,例如磁碟寫滿了,又或者從網路抓取數據的時候,網路連接突然崩潰等等。Python中內置了一套異常處理機制,可以幫助我們對這些錯誤進行處理。他就是try…except…finally的錯誤處理機制。
首先我們來看一個應用實例:
try: print('try...') r = / print('result:', r) except ZeroDivisionError as e: print('except:', e) finally: print('finally...') print('END')
上面的方法中,當我們認為某些程式碼可能存在一定的安全隱患的時候,可以使用try來運行這段程式碼,這樣做的好處是,如果這段程式碼真的存在錯誤,則後續的程式碼不會執行,而是會直接跳轉至錯誤處理程式碼,也就是except模組,執行完except之後,如果有finally語句,則執行finally語句,否則執行完畢,上面的程式碼很明顯,除數為0的演算法肯定是錯誤的。我們把它寫成一個函數test(),可以看到輸出值如下(程式碼可以左滑):
>>> def test(a): ... try: ... print('try...') ... r = / a ... print('result:', r) ... except ZeroDivisionError as e: ... print('except:', e) ... finally: ... print('finally...') ... print('END') ... return ... >>> a=test() try... ('result:', ) finally... END >>> b=test() try... ('except:', ZeroDivisionError('integer division or modulo by zero',)) finally... END >>>
我們輸入參數0的時候,函數返回了錯誤碼和相應的提示。
在上面的例子中,只定義了一種錯誤,實際情況中,可能有各種各樣的非法輸入,這就需要我們制定不同的except,從而對真正的錯誤原因進行區分:
>>> def test(a): ... try: ... print('try...') ... r = / int(a) ... print('result:', r) ... except ValueError as e: ... print('ValueError:', e) ... except ZeroDivisionError as e: ... print('ZeroDivisionError:', e) ... finally: ... print('finally...') ... print('END') ... >>> a=test('a') try... ('ValueError:', ValueError("invalid literal for int() with base 10: 'a'",)) finally... END >>> b=test() try... ('result:', ) finally... END >>> c=test() try... ('ZeroDivisionError:', ZeroDivisionError('integer division or modulo by zero',)) finally... END >>>
上面的結果可以看出,當我們輸入一個字母a的時候,返回的錯誤結果和輸入數字0的結果不同,但是他們都觸發了異常捕獲。
使用try…except還有另外一個好處,就是可以跨越多層調用,比如函數main()調用bar(),bar()調用foo(),如果foo()函數出錯了,此時,只要main函數捕獲到了,就可以進行處理:
def foo(s): return / int(s) def bar(s): return foo(s) * def main(): try: foo('0') except Exception as e: print('Error:', e) finally: print('finally...')
也就是說,不需要在每個可能出錯的地方去捕獲錯誤,只要在合適的層次去捕獲錯誤就可以了。這樣一來,就大大減少了寫try...except...finally
的麻煩。
如果錯誤沒有被捕獲,它就會一直往上拋,最後被Python解釋器捕獲,列印一個錯誤資訊,然後程式退出。來看一個錯誤的例子:
# err.py: def foo(s): return / int(s) def bar(s): return foo(s) * def main(): bar('0') main()
當我們調用這個文件的時候,會出現如下的錯誤:
$ python err.py Traceback (most recent call last): File "err.py", line , in <module> main() File "err.py", line , in main bar('0') File "err.py", line , in bar return foo(s) * File "err.py", line , in foo return / int(s) ZeroDivisionError: division by zero
層層分析,最後我們可以發現是在foo函數中使用0作為分母,從而出現了錯誤。這個過程中,我們可以看到函數的調用棧是由外而內的。
2
記錄錯誤,繼續執行
當出現錯誤的時候,如果我們想要繼續執行後面的程式,對當前的錯誤僅僅做一個捕獲操作,我們可以使用Python內置的logging模組:
# err_logging.py import logging def foo(s): return / int(s) def bar(s): return foo(s) * def main(): try: bar('0') except Exception as e: logging.exception(e) main() print('繼續執行,結果是...')
當我們對上面這段程式碼進行執行的時候,我們可以看到如下結果:
ERROR:root:integer division or modulo by zero Traceback (most recent call last): File "<stdin>", line , in main File "<stdin>", line , in bar File "<stdin>", line , in foo ZeroDivisionError: integer division or modulo by zero 繼續執行,結果是...
函數最終還是執行了print,但是將中途所有的錯誤都捕獲到了。這就是logging的作用,需要注意的是,在使用logging之前,先要對logging模組進行導入。通過配置,logging還可以把錯誤記錄到日誌文件中,方便以後排查。
3
拋出錯誤
在Python中,每一個錯誤都是一個class,所有的錯誤類型都繼承自BaseException,在使用except的時候需要注意,它不但不獲該類型的錯誤,還把其子類的錯誤一網打盡。捕獲一個錯誤就是捕獲該class的一個實例,Python內置的函數會拋出很多類型的錯誤,如果我們想自己自定義一個錯誤,可以使用下面的方法:
# err_raise.py class FooError(ValueError): pass def foo(s): n = int(s) if n==: raise FooError('invalid value: %s' % s) return / n foo('0')
執行上面這段程式碼,我們可以通過跟蹤找到我們自己定義的錯誤類型:
>>> python err_raise.py Traceback (most recent call last): File "<stdin>", line , in <module> File "<stdin>", line , in foo __main__.FooError: invalid value: >>>
只有在必要的時候才定義我們自己的錯誤類型。如果可以選擇Python已有的內置的錯誤類型(比如ValueError
,TypeError
),盡量使用Python內置的錯誤類型。