高級語言包括python一般都內置了一套try…catch…finally的錯誤處理機制:
>>> try: ... print('try...') ... r = 10 / 0 ... print('result:', r) ... except ZeroDivisionError as e: ... print('except:', e) ... finally: ... print('finally...') ... try... except: division by zero finally...
如果認為某些代碼可能會出錯,可以用try來運行這段代碼;
如果try的代碼塊出現錯誤,則try代碼省下的代碼不會繼續執行,而是直接跳轉到catch代碼塊,catch就是錯誤處理代碼塊(如果沒有錯誤,則不執行)
如果還有finally代碼塊,則執行finally代碼塊。沒有則不執行
我們看到代碼執行 10 / 0 的時候出現了錯誤(0不能作為除數),下面測試沒有錯誤的情況
>>> try: ... print('try……') ... r = 10 / 2 ... print('結果:%s' % r) ... except ZeroDivisionError as e: ... print('發生了異常:',e) ... finally: ... print('最后執行……') ... try…… 結果:5.0 最后執行……
如果try代碼塊可能出現多種錯誤類型,可以編寫多個except代碼塊來處理;此外,如果沒有發生錯誤,還可以在except代碼塊后面加上else語句,當沒有錯誤的時候,會自動執行else語句:
>>> try: ... print('開始:') ... r = 10 / int('2') ... print('結果:',r) ... except ValueError as e: ... print('ValueError:',e) ... except ZeroDivisionError as e: ... print('ZeroDivision:',r) ... else: ... print('沒有出錯!') ... finally: ... print('最后要執行的代碼') ... 開始: 結果: 5.0 沒有出錯! 最后要執行的代碼
萬物皆對象,python的錯誤也是class,所有的錯誤類型都繼承自BaseException,各個類型的錯誤之間可能會存在繼承關系,比如UnicodeError是ValueError的子類,如果catch語句中同時出現了這兩個錯誤,且UnicodeError在ValueError的后面處理的,那么永遠都捕獲不到UnicodeError。
下面是python中內置的常用錯誤類型繼承關系:
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning
使用try…catch…捕獲錯誤一個好處就是,可以跨層調用,比如main()調用foo(),foo()調用bar(),而錯誤是在bar中出現的,最后我們只需要在main()中捕獲就行:
>>> def foo(s): ... return 10 / int(s) ... >>> def bar(s): ... return foo(s)*2 ... >>> def main(): ... try: ... bar('0') ... except Exception as e: ... print('Error:',e) ... finally: ... print('最終要執行的代碼') ... >>> main() Error: division by zero 最終要執行的代碼
調用棧
如果沒有捕捉錯誤,該錯誤就會一直往上拋,最后被python解釋器捕獲,並打印一條錯誤消息,然后退出程序。下面新建一個err.py文件:
def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): bar('0') main()
執行結果:
PS E:\Python3.6.3\workspace> python err.py Traceback (most recent call last): File "err.py", line 8, in <module> main() File "err.py", line 6, in main bar('0') File "err.py", line 4, in bar return foo(s) * 2 File "err.py", line 2, in foo return 10 / int(s) ZeroDivisionError: division by zero
上面的信息我們可以看到整個錯誤的函數調用棧:
錯誤第一行:
Traceback (most recent call last):
告訴我們以下是錯誤的跟蹤信息。
第2~3行:
File "err.py", line 8, in <module> main()
告訴我們err.py執行中,main()出錯了,在代碼的第8行。
第4~5行:
File "err.py", line 6, in main bar('0')
告訴我們錯誤原因是第6行代碼。
依次往下,最終告訴我們是foo函數出錯了:
File "err.py", line 2, in foo return 10 / int(s)
這就是錯誤的源頭,因為控制台打印了錯誤類型:
ZeroDivisionError: division by zero
這是個ZeroDivisionError,我們分析並不是Int(s)本身定義或者語法有錯誤,而是int(s)返回值為0,從而找到了源頭。
上面說了當我們在程序中不捕獲錯誤的時候,python解釋器會在自動打印錯誤的堆棧,但是程序也會戛然而止。我們可以選擇把錯誤堆棧打印出來,同時程序會繼續執行下去。怎么操作呢?python內置的logging模塊可以非常清楚的記錄錯誤信息,新建一個err_logging.py文件:
import logging def foo(s): return 10 / int(s) def bar(s): return foo(s)*2 def main(): try: bar('0') except Exception as e: logging.exception(e) main() print('最后執行了……')
執行結果:
ERROR:root:division by zero Traceback (most recent call last): File "err_logging.py", line 8, in main bar('0') File "err_logging.py", line 5, in bar return foo(s)*2 File "err_logging.py", line 3, in foo return 10 / int(s) ZeroDivisionError: division by zero 最后執行了……
同樣出錯了,但是程序處理完錯誤信息后會繼續執行。
因為錯誤對象就是class,其實我們自己也可以自定義錯誤用於拋出。
首先,應該定義一個錯誤的類,選擇繼承關系,然后用raise關鍵字拋出實例,創建一個err_raise.py文件:
class FooError(ValueError): pass def foo(s): n = int(s) if n == 0 : raise FooError('非法的數值:%s' % s) return 10 / n foo('0')
執行后,如果有錯誤,我們可以追蹤到自己定義的錯誤:
PS E:\Python3.6.3\workspace> python err_raise.py Traceback (most recent call last): File "err_raise.py", line 9, in <module> foo('0') File "err_raise.py", line 6, in foo raise FooError('非法的數值:%s' % s) __main__.FooError: 非法的數值:0
有些時候我們會碰到一些當前代碼不適合處理或者不能處理的錯誤,我可以選擇記錄下錯誤之后,在向上拋,這時在except代碼塊中加入raise語句。raise語句還可以將錯誤類型轉化。