Python之異常處理

  • 2019 年 11 月 6 日
  • 筆記

Python之異常處理

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已有的內置的錯誤類型(比如ValueErrorTypeError),盡量使用Python內置的錯誤類型。