1. 程式人生 > >Python day 9(5) 錯誤處理

Python day 9(5) 錯誤處理

odi 取數據 see 註意 就是 查看 arc 自己的 peer

一:錯誤、調試和測試

1 程序運行中的錯誤:

a 程序編寫有問題造成的,這種錯誤我們通常稱之為bug,bug是必須修復的。

b 用戶輸入造成的,可以通過檢查用戶輸入來做相應的處理。

c 還有一類錯誤是完全無法在程序運行過程中預測的,比如寫入文件的時候,磁盤滿了,寫不進去了,或者從網絡抓取數據,網絡突然斷掉了。這類錯誤也稱為異常,在程序中通常是必須處理的,否則,程序會因為各種問題終止並退出。

2 調試:我們也需要跟蹤程序的執行,查看變量的值是否正確,這個過程稱為調試。Python的pdb可以讓我們以單步方式執行代碼。

3 測試:最後,編寫測試也很重要。有了良好的測試,就可以在程序修改後反復運行,確保程序輸出符合我們編寫的測試。

二:錯誤處理(用try......except......finally)

1 (用try......except......finally來捕獲錯誤)

1 try:
2     print(try...)
3     r = 10 / 0
4     print(result:, r)
5 except ZeroDivisionError as e:
6     print(except:, e)
7 finally:
8     print(finally...)
9 print(END)

a 當我們認為某些代碼可能會出錯時,就可以用try來運行這段代碼,如果執行出錯,則後續代碼不會繼續執行,而是直接跳轉至錯誤處理代碼,即except

語句塊,執行完except後,如果有finally語句塊,則執行finally語句塊,至此,執行完畢。

b 錯誤可能有很多種類,如果發生了不同類型的錯誤,應該由不同的except語句塊處理。沒錯,可以有多個except來捕獲不同類型的錯誤:

int()函數可能會拋出ValueError,所以我們用一個except捕獲ValueError,用另一個except捕獲ZeroDivisionError

此外,如果沒有錯誤發生,可以在except語句塊後面加一個else,當沒有錯誤發生時,會自動執行else語句:

 1 try:
 2     print(try...)
3 r = 10 / int(2) 4 print(result:, r) 5 except ValueError as e: 6 print(ValueError:, e) 7 except ZeroDivisionError as e: 8 print(ZeroDivisionError:, e) 9 else: 10 print(no error!) 11 finally: 12 print(finally...) 13 print(END)

c Python的錯誤其實也是class,所有的錯誤類型都繼承自BaseException,所以在使用except時需要註意的是,它不但捕獲該類型的錯誤,還把其子類也“一網打盡”。比如:

1 try:
2     foo()
3 except ValueError as e:
4     print(ValueError)
5 except UnicodeError as e:
6     print(UnicodeError)

第二個except永遠也捕獲不到UnicodeError,因為UnicodeErrorValueError的子類,如果有,也被第一個except給捕獲了。

Python所有的錯誤都是從BaseException類派生的,常見的錯誤類型和繼承關系看這裏:

https://docs.python.org/3/library/exceptions.html#exception-hierarchy

d 使用try...except捕獲錯誤還有一個巨大的好處,就是可以跨越多層調用,比如函數main()調用foo()foo()調用bar(),結果bar()出錯了,這時,只要main()捕獲到了,就可以處理:

 1 def foo(s):
 2     return 10 / int(s)
 3 
 4 def bar(s):
 5     return foo(s) * 2
 6 
 7 def main():
 8     try:
 9         bar(0)
10     except Exception as e:
11         print(Error:, e)
12     finally:
13         print(finally...)

也就是說,不需要在每個可能出錯的地方去捕獲錯誤,只要在合適的層次去捕獲錯誤就可以了。這樣一來,就大大減少了寫try...except...finally的麻煩。

2 調用棧(不用try......except......finally來捕獲)

如果錯誤沒有被捕獲,它就會一直往上拋,最後被Python解釋器捕獲,打印一個錯誤信息,然後程序退出。

 1 def foo(s):
 2     return 10 / int(s)
 3 
 4 def bar(s):
 5     return foo(s) * 2
 6 
 7 def main():
 8     bar(0)
 9 
10 main()

結果如下:

 1 $ python3 err.py
 2 Traceback (most recent call last):
 3   File "err.py", line 11, in <module>
 4     main()
 5   File "err.py", line 9, in main
 6     bar(0)
 7   File "err.py", line 6, in bar
 8     return foo(s) * 2
 9   File "err.py", line 3, in foo
10     return 10 / int(s)
11 ZeroDivisionError: division by zero

不過要自己分析錯誤的調用棧信息,定位錯誤的位置。

3 記錄錯誤

如果不捕獲錯誤,自然可以讓Python解釋器來打印出錯誤堆棧,但程序也被結束了。既然我們能捕獲錯誤,就可以把錯誤堆棧打印出來,然後分析錯誤原因,同時,讓程序繼續執行下去。

Python內置的logging模塊可以非常容易地記錄錯誤信息:

 1 import logging
 2 
 3 def foo(s):
 4     return 10 / int(s)
 5 
 6 def bar(s):
 7     return foo(s) * 2
 8 
 9 def main():
10     try:
11         bar(0)
12     except Exception as e:
13         logging.exception(e)
14 
15 main()
16 print(END)

同樣是出錯,但程序打印完錯誤信息後會繼續執行,並正常退出

 1 $ python3 err_logging.py
 2 ERROR:root:division by zero
 3 Traceback (most recent call last):
 4   File "err_logging.py", line 13, in main
 5     bar(0)
 6   File "err_logging.py", line 9, in bar
 7     return foo(s) * 2
 8   File "err_logging.py", line 6, in foo
 9     return 10 / int(s)
10 ZeroDivisionError: division by zero
11 END

通過配置,logging還可以把錯誤記錄到日誌文件裏,方便事後排查。

4 拋出錯誤

因為錯誤是class,捕獲一個錯誤就是捕獲到該class的一個實例。因此,錯誤並不是憑空產生的,而是有意創建並拋出的。Python的內置函數會拋出很多類型的錯誤,我們自己編寫的函數也可以拋出錯誤。

如果要拋出錯誤,首先根據需要,可以定義一個錯誤的class,選擇好繼承關系,然後,用raise語句拋出一個錯誤的實例

 1 # err_raise.py
 2 class FooError(ValueError):
 3     pass
 4 
 5 def foo(s):
 6     n = int(s)
 7     if n==0:
 8         raise FooError(invalid value: %s % s)
 9     return 10 / n
10 
11 foo(0)

執行,可以最後跟蹤到我們自己定義的錯誤

1 $ python3 err_raise.py 
2 Traceback (most recent call last):
3   File "err_throw.py", line 11, in <module>
4     foo(0)
5   File "err_throw.py", line 8, in foo
6     raise FooError(invalid value: %s % s)
7 __main__.FooError: invalid value: 0

只有在必要的時候才定義我們自己的錯誤類型。如果可以選擇Python已有的內置的錯誤類型(比如ValueErrorTypeError),盡量使用Python內置的錯誤類型。

Python day 9(5) 錯誤處理