您好,登錄后才能下訂單哦!
如何動態捕獲Python異常,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
在討論動態捕獲異常時讓我大吃一驚的是,可以讓我找到隱藏的Bug和樂趣。
有問題的代碼
下面的代碼來自一個產品中看起來是好的抽象代碼 - slightly(!) .這是調用一些統計數據的函數,然后進行處理 . 首先是用socket連接獲取一個值,可能發生了socket錯誤.由于統計數據在系統中不是至關重要的,我們只是記一下日志錯誤并繼續往下走.
(請注意,這篇文章我使用doctest測試的 - 這代表代碼可以運行!)
>>> def get_stats(): ... pass ... >>> def do_something_with_stats(stats): ... pass ... >>> try: ... stats = get_stats() ... except socket.error: ... logging.warning("Can't get statistics") ... else: ... do_something_with_stats(stats)
查找
我們測試時并沒有發現不妥, 但實際上我們注意到靜態分析報告顯示一個問題:
$ flake8 filename.py filename.py:351:1: F821 undefined name 'socket' filename.py:352:1: F821 undefined name 'logging'
顯然是我們沒測試,這個問題是代碼中我們沒有引用socket 和 logging 兩個模塊.使我感到驚奇的是,這并沒有預先拋出NameError錯,我以為它會查找這些異常語句中的一些名詞,如它需要捕捉這些異常,它需要知道些什么呢!
事實證明并非如此,異常語句的查找是延遲完成的,只是評估時拋出異常. 不只是名稱延遲查找,也可以定制顯示聲明異常做為'參數(argument)'.
這可能是好事,壞事,或者是令人厭惡的.
好事(上段中提到的)
異常參數可以以任意形式數值傳遞. 這樣就允許了異常的動態參數被捕獲.
>>> def do_something(): ... blob ... >>> def attempt(action, ignore_spec): ... try: ... action() ... except ignore_spec: ... pass ... >>> attempt(do_something, ignore_spec=(NameError, TypeError)) >>> 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: ... a, b = do_something() ... except ValuError: # oops - someone can't type ... print("Oops") ... else: ... print("OK!") # we are 'ok' until do_something returns a triple... OK!
令人討厭的(上段中提到的)
>>> try: ... TypeError = ZeroDivisionError # now why would we do this...?! ... 1 / 0 ... except TypeError: ... print("Caught!") ... else: ... print("ok") ... Caught!
不僅僅是異常參數通過名稱查找, - 其它的表達式也是這樣工作的:
>>> try: ... 1 / 0 ... except eval(''.join('Zero Division Error'.split())): ... print("Caught!") ... else: ... print("ok") ... Caught!
異常參數不僅僅只能在運行時確定,它甚至可以使用在生命周期內的異常的信息. 以下是一個比較費解的方式來捕捉拋出的異常 - 但也只能如此了:
>>> import sys >>> def current_exc_type(): ... return sys.exc_info()[0] ... >>> try: ... 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: ... print("bad") ... else: ... print("good") ... >>> dis.dis(x) # doctest: +NORMALIZE_WHITESPACE 2 0 SETUP_EXCEPT 4 (to 7) <BLANKLINE> 3 3 POP_BLOCK 4 JUMP_FORWARD 22 (to 29) <BLANKLINE> 4 >> 7 DUP_TOP 8 LOAD_GLOBAL 0 (Blobbity) 11 COMPARE_OP 10 (exception match) 14 POP_JUMP_IF_FALSE 28 17 POP_TOP 18 POP_TOP 19 POP_TOP <BLANKLINE> 5 20 LOAD_CONST 1 ('bad') 23 PRINT_ITEM 24 PRINT_NEWLINE 25 JUMP_FORWARD 6 (to 34) >> 28 END_FINALLY <BLANKLINE> 7 >> 29 LOAD_CONST 2 ('good') 32 PRINT_ITEM 33 PRINT_NEWLINE >> 34 LOAD_CONST 0 (None) 37 RETURN_VALUE
這顯示出了我原來預期的問題(issue). 異常處理"看起來"完全是按照Python內部機制在運行. 這一步完全沒有必要知道關于后續的異常“捕獲”語句, 并且如果沒有異常拋出它們將被完全忽略了.SETUP_EXCEPT并不關心發生了什么, 僅僅是如果發生了異常, ***個處理程序應該被評估,然后第二個,以此類推.
每個處理程序都有兩部分組成: 獲得一個異常的規則, 和剛剛拋出的異常進行對比. 一切都是延遲的, 一切看起來正如對你的逐行的代碼的預期一樣, 從解釋器的角度來考慮. 沒有任何聰明的事情發生了,只是突然使得它看起來非常聰明.
總結
雖然這種動態的異常參數讓我大吃一驚, 但是這當中包含很多有趣的應用. 當然去實現它們當中的許多或許是個餿主意,呵呵
有時并不能總是憑直覺來確認有多少Python特性的支持 - 例如 在類作用域內 表達式和聲明都是被顯式接受的, (而不是函數, 方法, 全局作用域),但是并不是所有的都是如此靈活的. 雖然(我認為)那將是十分美好的, 表達式被禁止應用于裝飾器 - 以下是Python語法錯誤:
@(lambda fn: fn) def x(): pass
這個是嘗試動態異常參數通過給定類型傳遞給***個異常的例子, 靜靜的忍受重復的異常:
>>> class Pushover(object): ... exc_spec = set() ... ... def attempt(self, action): ... try: ... return action() ... except tuple(self.exc_spec): ... pass ... except BaseException as e: ... self.exc_spec.add(e.__class__) ... raise ... >>> pushover = Pushover() >>> >>> for _ in range(4): ... try: ... pushover.attempt(lambda: 1 / 0) ... except: ... print ("Boo") ... else: ... print ("Yay!") Boo Yay! Yay! Yay!
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。