Python必學原始碼庫,Python的錯誤處理方法!
在程式執行的過程中,如果發生了錯誤,可以事先約定返回一個錯誤程式碼,這樣,就可以知道是否有錯,以及出錯的原因。在作業系統提供的呼叫中,返回錯誤碼非常常見。比如開啟檔案的函式open()
,成功時返回檔案描述符(就是一個整數),出錯時返回-1
。
用錯誤碼來表示是否出錯十分不便,因為函式本身應該返回的正常結果和錯誤碼混在一起,造成呼叫者必須用大量的程式碼來判斷是否出錯:
def foo():
r = some_function()
if r==(-1): return (-1) # do something return r def bar(): r = foo() if r==(-1): print('Error') else: pass
如果你覺得看這些理論知識乏味不夠形象,可以進他們的Python技術扣扣裙【278136312】 裙公告裡面有講的非常詳細的完整版Python入門到實戰+筆記+原始碼+作業視訊教程免費下載,生動的講解可以讓你思路更清晰,遇到問題裡面有大佬解答指
一旦出錯,還要一級一級上報,直到某個函式可以處理該錯誤(比如,給使用者輸出一個錯誤資訊)。
所以高階語言通常都內建了一套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
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')
如果你覺得看這些理論知識乏味不夠形象,可以進他們的Python技術扣扣裙【278136312】 裙公告裡面有講的非常詳細的完整版Python入門到實戰+筆記+原始碼+作業視訊教程免費下載,生動的講解可以讓你思路更清晰,遇到問題裡面有大佬解答指
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...except
捕獲錯誤還有一個巨大的好處,就是可以跨越多層呼叫,比如函式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
的麻煩。
呼叫棧
如果錯誤沒有被捕獲,它就會一直往上拋,最後被Python直譯器捕獲,列印一個錯誤資訊,然後程式退出。來看看err.py
:
# err.py:
def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): bar('0') main()
執行,結果如下:
$ python3 err.py
Traceback (most recent call last):
File "err.py", line 11, in <module> main() File "err.py", line 9, in main bar('0') File "err.py", line 6, in bar return foo(s) * 2 File "err.py", line 3, in foo return 10 / int(s) ZeroDivisionError: division by zero
出錯並不可怕,可怕的是不知道哪裡出錯了。解讀錯誤資訊是定位錯誤的關鍵。我們從上往下可以看到整個錯誤的呼叫函式鏈:
錯誤資訊第1行:
Traceback (most recent call last):
告訴我們這是錯誤的跟蹤資訊。
第2~3行:
File "err.py", line 11, in <module>
main()
呼叫main()
出錯了,在程式碼檔案err.py
的第11行程式碼,但原因是第9行:
File "err.py", line 9, in main
bar('0')
呼叫bar('0')
出錯了,在程式碼檔案err.py
的第9行程式碼,但原因是第6行:
File "err.py", line 6, in bar
return foo(s) * 2
原因是return foo(s) * 2
這個語句出錯了,但這還不是最終原因,繼續往下看:
File "err.py", line 3, in foo
return 10 / int(s)
原因是return 10 / int(s)
這個語句出錯了,這是錯誤產生的源頭,因為下面列印了:
ZeroDivisionError: integer division or modulo by zero
根據錯誤型別ZeroDivisionError
,我們判斷,int(s)
本身並沒有出錯,但是int(s)
返回0
,在計算10 / 0
時出錯,至此,找到錯誤源頭。