溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何動態(tài)捕獲Python異常

發(fā)布時間:2021-10-28 16:45:38 來源:億速云 閱讀:182 作者:柒染 欄目:編程語言

如何動態(tài)捕獲Python異常,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

在討論動態(tài)捕獲異常時讓我大吃一驚的是,可以讓我找到隱藏的Bug和樂趣。

有問題的代碼

下面的代碼來自一個產(chǎn)品中看起來是好的抽象代碼 - slightly(!) .這是調(diào)用一些統(tǒng)計數(shù)據(jù)的函數(shù),然后進行處理 . 首先是用socket連接獲取一個值,可能發(fā)生了socket錯誤.由于統(tǒng)計數(shù)據(jù)在系統(tǒng)中不是至關(guān)重要的,我們只是記一下日志錯誤并繼續(xù)往下走.

(請注意,這篇文章我使用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)

查找

我們測試時并沒有發(fā)現(xiàn)不妥, 但實際上我們注意到靜態(tài)分析報告顯示一個問題:

$ flake8 filename.py  filename.py:351:1: F821 undefined name 'socket' filename.py:352:1: F821 undefined name 'logging'

顯然是我們沒測試,這個問題是代碼中我們沒有引用socket 和 logging 兩個模塊.使我感到驚奇的是,這并沒有預先拋出NameError錯,我以為它會查找這些異常語句中的一些名詞,如它需要捕捉這些異常,它需要知道些什么呢!

事實證明并非如此,異常語句的查找是延遲完成的,只是評估時拋出異常. 不只是名稱延遲查找,也可以定制顯示聲明異常做為'參數(shù)(argument)'.

這可能是好事,壞事,或者是令人厭惡的.

好事(上段中提到的)

異常參數(shù)可以以任意形式數(shù)值傳遞. 這樣就允許了異常的動態(tài)參數(shù)被捕獲.

>>> 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

壞事(上段中提到的)

這種明顯的弊端就是異常參數(shù)中的錯誤通常只有在異常觸發(fā)之后才會被注意到,不過為時已晚.當用異常去捕獲不常見的事件時(例如:以寫方式打開文件失敗), 除非做個一個特定的測試用例,否則只有當一個異常(或者任何異常)被觸發(fā)的時候才會知道, 屆時記錄下來并且查看是否有匹配的異常, 并且拋出它自己的錯誤異常 - 這是一個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!

不僅僅是異常參數(shù)通過名稱查找, - 其它的表達式也是這樣工作的:

>>> try:  ...     1 / 0 ... except eval(''.join('Zero Division Error'.split())):  ...     print("Caught!")  ... else:  ...     print("ok")  ...  Caught!

異常參數(shù)不僅僅只能在運行時確定,它甚至可以使用在生命周期內(nèi)的異常的信息. 以下是一個比較費解的方式來捕捉拋出的異常 - 但也只能如此了:

>>> import sys  >>> def current_exc_type():  ...     return sys.exc_info()[0]  ...  >>> try:  ...     blob  ... except current_exc_type():  ...     print ("Got you!")  ...  Got you!

很明顯這才是我們真正要尋找的當我們寫異常處理程序時, 我們應(yīng)該首先想到的就是這種

(字節(jié))代碼

為了確認它是如何在異常處理工作中出現(xiàn)的,我在一個異常的例子中運行 dis.dis(). (注意 這里的分解是在Python2.7 下 - 不同的字節(jié)碼是Python 3.3下產(chǎn)生的,但這基本上是類似的):

>>> 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內(nèi)部機制在運行. 這一步完全沒有必要知道關(guān)于后續(xù)的異?!安东@”語句, 并且如果沒有異常拋出它們將被完全忽略了.SETUP_EXCEPT并不關(guān)心發(fā)生了什么, 僅僅是如果發(fā)生了異常, ***個處理程序應(yīng)該被評估,然后第二個,以此類推.

每個處理程序都有兩部分組成: 獲得一個異常的規(guī)則, 和剛剛拋出的異常進行對比. 一切都是延遲的, 一切看起來正如對你的逐行的代碼的預期一樣, 從解釋器的角度來考慮. 沒有任何聰明的事情發(fā)生了,只是突然使得它看起來非常聰明.

總結(jié)

雖然這種動態(tài)的異常參數(shù)讓我大吃一驚, 但是這當中包含很多有趣的應(yīng)用. 當然去實現(xiàn)它們當中的許多或許是個餿主意,呵呵

有時并不能總是憑直覺來確認有多少Python特性的支持 - 例如 在類作用域內(nèi) 表達式和聲明都是被顯式接受的, (而不是函數(shù), 方法, 全局作用域),但是并不是所有的都是如此靈活的. 雖然(我認為)那將是十分美好的, 表達式被禁止應(yīng)用于裝飾器 - 以下是Python語法錯誤:

@(lambda fn: fn)  def x():     pass

這個是嘗試動態(tài)異常參數(shù)通過給定類型傳遞給***個異常的例子, 靜靜的忍受重復的異常:

>>> 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!

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI