python動態捕獲異常

來源:互聯網
上載者:User
在討論動態捕獲異常時讓我大吃一驚的是,可以讓我找到隱藏的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)

3 3 POP_BLOCK

4 JUMP_FORWARD 22 (to 29)

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

5 20 LOAD_CONST 1 ('bad')

23 PRINT_ITEM

24 PRINT_NEWLINE

25 JUMP_FORWARD 6 (to 34)

>> 28 END_FINALLY

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!

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.