天天看點

python錯誤處理—try…catch…finally、調用棧分析

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