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語句還可以將錯誤類型轉化。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM