try…except…finally…
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
將可能錯誤的代碼放在try
中測試,如果真的錯誤將直接跳轉到except
語句塊。無論有沒有發生錯誤,如果有finally
就執行finally
,也可以沒有finally
語句。
運行結果
try...
except: division by zero
finally...
END
錯誤本身是一個類(class),所有的錯誤類型繼承自BaseException
,可以用多個except
捕獲不同的Error
可以在except
後加else
,except
沒有執行時執行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')
由於繼承關係,當捕獲父類的錯誤時,子類將被忽略
try:
foo()
except ValueError as e:
print('ValueError')
except UnicodeError as e:
print('UnicodeError')
第二個except
永遠也捕獲不到UnicodeError
,因爲UnicodeError
是ValueError
的子類,如果有,也被第一個except
給捕獲了。
常見錯誤類型和繼承關係
使用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...')
不需要在foo
,bar
函數中去捕獲錯誤
調用堆棧
如果錯誤沒有被捕獲,它就會一直往上拋,最後被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')
同樣是出錯,但程序打印完錯誤信息後會繼續執行,並正常退出。
通過配置,logging還可以把錯誤記錄到日誌文件裏,方便事後排查。
拋出錯誤raise
如果要拋出錯誤,首先根據需要,可以定義一個錯誤的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')
只有在必要的時候才定義我們自己的錯誤類型,儘量使用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('ValueError!')
raise
bar()
在bar()
函數中,打印一個ValueError!
後,又把錯誤通過raise
語句拋出去
捕獲錯誤目的只是記錄一下,便於後續追蹤。但是,由於當前函數不知道應該怎麼處理該錯誤,所以,最恰當的方式是繼續往上拋,讓頂層調用者去處理。好比一個員工處理不了一個問題時,就把問題拋給他的老闆,如果他的老闆也處理不了,就一直往上拋,最終會拋給CEO去處理。
raise
語句如果不帶參數,就會把當前錯誤原樣拋出。此外,在except中raise
一個Error
,還可以把一種類型的錯誤轉化成另一種類型:
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
上面有except
時raise
與直接打印print
錯誤的區別主要是raise
可以返回更詳細的錯誤信息