python錯誤處理—try…catch…finally、調用棧分析
進階語言包括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語句還可以将錯誤類型轉化。
posted on 2018-03-15 13:26 風雨一肩挑 閱讀( ...) 評論( ...) 編輯 收藏
轉載于:https://www.cnblogs.com/hiwuchong/p/8573081.html