在討論動態捕獲異常時讓我大吃一驚的是,可以讓我找到隱藏的Bug和樂趣...
有問題的代碼
下面的代碼來自一個產品中看起來是好的抽象代碼- slightly(!) .這是調用一些統計數據的函數,然後進行處理. 首先是用socket連接獲取一個值,可能發生了socket錯誤.由於統計數據在系統中不是至關重要的,我們只是記一下日誌錯誤並繼續往下走.
(請注意,這篇文章我使用doctest測試的- 這代表程式碼可以運行!)
>>> def get_stats():
... pass
.. .
>>> def do_something_with_stats(stats):
... pass
...
>>> try:
... .
... logging.warning("Can't get statistics")
... else:
... do_something_with_stats(stats)
但其實我們注意到靜態分析報告顯示一個問題:
$ flake8 filename.py
. +/of...
... >>> attempt(do_something, ignore_spec=TypeError)Traceback (most recent call last): ...NameError: global name 'blob' is not defined的弊端就是異常參數中的錯誤通常只有在異常觸發之後才會被注意到,不過為時已晚.當用異常去捕獲不常見的事件時(例如:以寫方式打開文件失敗),除非做個一個特定的測試用例,否則只有當一個異常(或任何異常)被觸發的時候才會知道, 屆時記錄下來並且查看是否有匹配的異常, 並且拋出它自己的錯誤異常- 這是一個NameError通常所做的事情.
>>> def do_something():
... return 1, 2
...
>>> try:
...
, b>> .. except ValuError: # oops - someone can't type... print("Oops")... else:... . do_something returns a triple...OK!討厭的(上段中提到的)>>> try:... TypeError = ZeroDivisionrror this nowr woo !... except 類型... 1 / 0
... except TypeError:
... print("Caught!")
... else:o ..
Caught!
不僅僅是異常參數通過名稱查找, - 其它的表達式也是這樣工作的:
>>> try:
... 1 / 0
... except eval(... ''.join('Zero Division Error'.split())):
... print("Caught!")
... else:
... print("ok")
... print("ok"). ..Caught!異常參數不僅僅只能在運行時確定,它甚至可以使用在生命週期內的異常的信息. 以下是一個比較費解的方式來捕捉拋出的異常- 但也只能如此了:>>> import sys>>> def current_exc_type():... return sys.exc_info()[0]......
>>
. blob... except current_exc_type():... print ("Got you!")...Got you!異常處理程序時, 我們應該首先想到的就是這種(字節)代碼為了確認它是如何在異常處理工作中出現的,我在一個異常的例子中運行dis.dis(). (注意這裡的分解是在Python2.7 下- 不同的字節碼是Python 3.3下產生的,但這基本上是類似的):>>> import dis>>> def x():... try:... pass... except Blobbity:... except Blobbity:...
... else:
... print("good")
...
>>> dis.dis(x) CEPT 4 ( 7)
4跳躍_forward 22(至29)
8load_global 0(blobbity) _if_if_if_false28
1717 pop_top
18pop_top
11pop_top
520 load_const 1('bad')23print_item7 >> 29 LOAD_CONST 2(「良好」)
32 PRINT_ITEM
33 PRINT_NEWLINE
37 RETURN_VALUE
這顯示了我最初預期的問題(問題)。 異常處理「looks」完全是依照Python運作時的內部機制。 這一步驟沒有必要知道關於後續的異常「捕獲」語句,並且如果沒有異常拋出它們將被完全忽略了。 SETUP_EXCEPT並不關心發生什麼,如果發生了異常,第一個處理程序應該被忽略評估,然後第二個,以此類推。
... 除了BaseException 作為e:... self.exc_spec.add(e.__class > Pushover = Pushover()>> > >>> for _ in range(4):... try:... Pushover.attempt(lambda: 1 / 0) ex ( "噓」)... 其他:... 印刷(「耶!」)噓耶! 耶! 耶!