1、python的錯誤處理 在程序運行的過程當中,若是發生了錯誤,能夠事先約定返回一個錯誤代碼,這樣,就能夠知道是否有錯以及出錯的緣由。 在操做系統提供的調用中,返回錯誤碼很是常見。好比打開文件的函數open(),成功時返回文件的描述符(就是一個整數),出錯時返回-1 用錯誤碼來表示是否出錯十分不便,由於函數自己應該返回的正常結果和錯誤碼混在一塊兒,形成調用者必須大量的代碼來判斷是否出錯: def foo(): r = somefunction() if r == (-1): return (-1) return r def bar(): r = foo() if r == (-1): print("Error") else: pass 一旦出錯,還要一級一級上報,直到某個函數能夠處理該錯誤(好比,給用戶輸出一個錯誤信息) 因此,高級語言一般都內置了一套try...except...finally...的錯誤處理機制,python也不例外。 try 讓咱們用一個例子來看看try的機制 try: print("try....") r = 10 / 0 print("result", r) except ZeroDivisionError as e: print("except:", e) finally: print("finally...") print("END....") 當咱們認爲某些代碼可能會出錯時,就能夠用try來運行這段代碼,若是執行出錯,則後續代碼不會繼續執行 而是直接跳轉至錯誤處理代碼,即except語句塊 執行完except後,若是有finally語句塊,則執行finally語句塊,至此,執行完畢。 上面的代碼在計算10 / 0時 會產生一個除法運算錯誤: try.... except: division by zero finally... END.... >>> 從輸出能夠看到,當錯誤發生時,後續語句print("result:", r)不會被執行,except因爲捕獲到ZeroDivisionError所以被執行。 最後,finally語句被執行。而後,程序繼續按照流程往下走。 若是把除數0 變成2,則執行結果以下 try.... result 5.0 finally... END.... >>> 因爲沒有錯誤發生,因此except語句塊不會被執行,可是finally若是有則必定會被執行,固然finally也能夠沒有 你還能夠猜想,錯誤應該有不少種類,日過發生了不一樣類型的錯誤,應該由不一樣的except語句塊處理。 沒錯,能夠有多個except來捕獲不一樣類型的錯誤: try: print("try.....") r = 10 / int("a") print("result:", r) except ValueError as e: print("ValueError:", e) except ZeroDivisionError as e: print("ZeroDivisionError:", e) finally: print("finally...") print("END...") int()函數可能會拋出ValueError,因此咱們用一個except捕獲ValueError,用另外一個except捕獲ZeroDivisionError 此外,若是沒有錯誤發生,能夠再except語句塊後面加一個else,當沒有錯誤發生時,會自動執行else語句。 try: print("try...") r = 10 / int("2") print("result:", r) except ValueError as e: print("ValueError:", e) except ZeroDivisionError as e: print("ZeroDivisionError:", e) else: print("No error!") finally: print("finally...") print("END") python的錯誤其實也是class,全部的錯誤類型都繼承自BaseException, 因此在使用except時須要注意的是,它不但捕獲該類型的錯誤,還把其子類也「一網打盡」。 好比: try: foo() except ValueError as e: print("ValueError") except UnicodeError as e: print("UnicodeError") 第二個except永遠也捕獲不到UnicodeError, 由於UnicodeError是ValueError的子類 若是有,也是被第一個except給捕獲了。 python全部的錯誤都是BaseException類派生的。 全部常見的錯誤類型和繼承關係看這裏: https://docs.python.org/3/library/exceptions.html#exception-hierarchy 使用try...exccept捕獲錯誤還有一個巨大的好處,就是能夠跨越多層調用,好比函數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("finally...") 也就是說,不須要在每一個可能出錯的地方去捕獲異常,只要在合適的層次去捕獲就能夠了。 這樣一來,就大大減小了寫 try...except...finally的麻煩。 2、調用堆棧 若是錯誤沒有被捕獲,他就會一直往上拋,最後被python解釋器捕獲,打印一個錯誤信息,而後程序退出。 def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): bar("0") main() 執行結果爲: Traceback (most recent call last): File "C:/Python36/test.py", line 10, in <module> main() File "C:/Python36/test.py", line 8, in main bar("0") File "C:/Python36/test.py", line 5, in bar return foo(s) * 2 File "C:/Python36/test.py", line 2, in foo return 10 / int(s) ZeroDivisionError: division by zero 出錯並不可怕,可怕的時不知道哪裏出錯了。解讀錯誤信息時定位錯誤的關鍵。 咱們從上往下能夠看到整個錯誤的調用函數鏈。 錯誤第一行: Traceback (most recent call last): 這告訴咱們的是錯誤的跟蹤信息。 File "C:/Python36/test.py", line 10, in < module > main() 說明調用main()出錯了,在代碼文件test.py中第10行,可是緣由是第8行: File"C:/Python36/test.py", line8, in main bar("0") 調用bar("0")出錯了,在代碼文件test.py中第8行,但緣由是第5行: File"C:/Python36/test.py", line5, in bar return foo(s) * 2 調用return foo(s) * 2時出錯了,在test.py中第5行,但緣由是第2行 File "C:/Python36/test.py", line 2, in foo return 10 / int(s) ZeroDivisionError: division by zero 這時咱們找到了源頭,原來在第2行調用return 10 / int(s)出錯了,錯誤爲ZeroDivisionError 3、記錄錯誤 若是不捕獲錯誤,天然可讓python解釋器來打印出錯誤堆棧,可是程序也被結束了。 既然咱們能捕獲錯誤,就能夠把錯誤堆棧打印出來,而後分析錯誤緣由,同時,讓程序繼續執行下去。 python內置的logging模塊能夠很是容易地記錄錯誤信息: 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("END") 輸出結果爲: ERROR:root:division by zero Traceback (most recent call last): File "C:/Python36/test.py", line 12, in main bar("0") File "C:/Python36/test.py", line 8, in bar return foo(s) * 2 File "C:/Python36/test.py", line 5, in foo return 10 / int(s) ZeroDivisionError: division by zero END 一樣是出錯,但程序打印完錯誤信息後會繼續執行,並正常退出。 經過配置,logging還能夠把錯誤記錄到日誌文件裏,方便過後排查。 4、拋出錯誤 由於錯誤是class,捕獲一個錯誤就是捕獲到該class的一個實例。 所以,錯誤並非憑空產生的,而是有意建立並拋出的。 python的內置函數會拋出不少類型的錯誤,咱們本身編寫的函數也能夠拋出錯誤。 若是要拋出錯誤,首先根據須要,能夠定義一個錯誤的class,選擇好繼承關係,而後用raise語句拋出一個錯誤的實例: class FooError(ValueError): pass def foo(s): n = int(s) if n == 0: raise FooError("invalid value: %s" % s) return 10 / n foo("0") 輸出結果: Traceback (most recent call last): File "C:/Python36/test.py", line 10, in <module> foo("0") File "C:/Python36/test.py", line 7, in foo raise FooError("invalid value: %s" % s) FooError: invalid value: 0 只有在必要的時候才定義咱們本身的錯誤類型。 若是能夠選擇python已有的內置錯誤類型(好比ValueError, TypeError),儘可能使用python內置的錯誤類型。 最後,咱們來看另外一種錯誤處理方式: def foo(s): n = int(s) if n == 0: raise ValueError("invalid value: %s" % s) return 10 / n def bar(): try: foo("0") except ValueError as e: print("ValieError") raise bar() 在bar()函數中,咱們明明已經捕獲了錯誤,可是,打印一個ValueError以後 又經過raise語句拋出去了。這不是有病嗎 其實,這種錯誤處理方式不但沒病,並且至關常見。 捕獲錯誤目的只是記錄一下,便於或許追蹤。 可是,因爲當前函數不知道應該怎麼處理該錯誤,因此,最恰當的方式是繼續往上拋,讓頂層調用者去處理。 比如一個員工處理不了一個問題時,就把問題一直往上拋,最終會拋給CEO去解決。 注意:raise語句若是不帶參數,就會把當前錯誤原樣拋出。 此外,在except中raise一個Error,還能夠改寫錯誤類型 try: 10 / 0 except ZeroDivisionError: raise ValueError("do not input zero!") 輸出結果: Traceback (most recent call last): File "C:/Python36/test.py", line 4, in <module> raise ValueError("do not input zero!") ValueError: do not input zero! >>> 只要是合理的轉換邏輯就能夠,可是,毫不應該把一個IOError轉成絕不相干的valueError. 總結: python內置的 try...except...finally 用來處理錯誤十分方便。 出錯時,會分析錯誤信息並定位錯誤發生的代碼位置纔是關鍵的。 程序也能夠主動拋出錯誤,讓調用者來處理相應的錯誤。 可是應該在文檔中寫清楚可能會拋出哪些錯誤,以及錯誤產生的緣由。 來源:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143191375461417a222c54b7e4d65b258f491c093a515000