python try...finally...的實現方法
1. 關於 try.. finally..
假如上帝用 python 為每一個來到世界的生物編寫程式,那麼除去中間過程的種種複雜實現,最不可避免的就是要保證每個例項最後都要掛掉。程式碼可簡寫如下:
try: born() # 出生 # 正常降臨世界 # do something.. except ValueError: # 安排錯誤 # do something... except AttributeError: # 特徵錯誤 # do something... except TypeError: # 種類錯誤 # do something... ... # 等等雜七雜八的錯誤 finally: go_die() # 掛掉 come_to_see_me() # 然後來見我 reincarnate() # 下一輪,安排!
這就是 finally
的作用和例項。就算捕獲異常後再次出現異常,最終也能保證 go_die
方法會執行,但是,如果 go_die
方法出現錯誤,那麼就不能正常去見上帝了。為了保證每個生物(不管有沒有掛掉)都能見到上帝他老人家,並開始下一個輪迴(不管有沒有見到),需要做如下處理:
... finally: try: go_die() finally: try: come_to_see_me() finally: reincarnate()
OK,功能雖然實現了,但按照 The Zen of Python
所說:Flat is better than nested.(扁平優於巢狀),那麼這段程式碼就略顯醜陋了。為了遵循 python 美學,我們可以對這段進行優化,使它看起來更為美觀。
2. 錯誤的上下文:__context__
在此之前,需要引入一個新的概念: __context__
,__context__
的字面意思就是上下文,它屬於錯誤的一個屬性。在錯誤捕獲中,它意味著當你處理一個錯誤時,另一個錯誤發生了。也就是說,你所捕獲的錯誤雖然被成功捕獲了,但當捕獲完成時,你的一些操作導致另一個錯誤發生,而這個錯誤並沒有被捕獲。通常情況下,如果處理的好,那麼當前錯誤的 __context__
的值為 None
,如果處理不好那就是你所捕獲的錯誤。比如下面的程式碼:
def type_err(): raise TypeError('this is a type error.') def after_type_err(): raise ValueError('this is a value error.') try: type_err() except TypeError: after_type_err()
執行結果為:
Traceback (most recent call last): File "<ipython-input-4-189a22d65266>",line 8,in <module> type_err() File "<ipython-input-4-189a22d65266>",line 2,in type_err raise TypeError('this is a type error.') TypeError: this is a type error. During handling of the above exception,another exception occurred: Traceback (most recent call last): File "C:\Users\lineu\AppData\Local\Programs\Python\Python37\lib\site-packages\IPython\core\interactiveshell.py",line 3326,in run_code exec(code_obj,self.user_global_ns,self.user_ns) File "<ipython-input-4-189a22d65266>",line 10,in <module> after_type_err() File "<ipython-input-4-189a22d65266>",line 5,in after_type_err raise ValueError('this is a value error.') ValueError: this is a value error.
在上面的錯誤資訊中,當前錯誤型別為 ValueError
,它的 __context__
屬性值為 TypeError
例項,而 TypeError 例項的 __context__
為 None
。
3. FinalExecutor:優雅的 finally
有了 __context__
的概念,我們就可以基於此實現一個優雅的“輪迴”了。基本思路為:依次執行方法,如果方法報錯,那麼就將該錯誤的 __context__
值設定為上一個錯誤(如果有)。最後等到所有方法執行完畢,再丟擲最後一個錯誤,那麼此時的錯誤將包含所有可能被引發的錯誤資訊。具體程式碼如下:
class FinalExecutor(object): """終極執行器 用於確保你所有的方法都會被執行(不管中途有沒有方法報錯) 同時能看到正確的錯誤資訊 """ def __init__(self): self.last_err = None # 儲存最近發生的錯誤 def __enter__(self): return self def __exit__(self,exc_type,exc_val,exc_tb): # 如果有發生錯誤,則丟擲 if self.last_err: raise self.last_err def call(self,func,*args,**kwargs): """呼叫執行方法""" try: func(*args,**kwargs) except Exception as e: # Exception 捕獲所有繼承自它或它子類的錯誤型別 # 捕獲它等於捕獲幾乎所有錯誤 if self.last_err: # 將本次錯誤的上下文定義為上一次錯誤 e.__context__ = self.last_err # 更新為當前錯誤 self.last_err = e
我們的終極執行器使用示例為:
# 定義 3 個方法用於測試 def type_err(): print('type error') raise TypeError('x') def value_err(): print('value error') raise ValueError('x') def attr_err(): print('attr error') raise AttributeError('x') # 使用 with 語句來啟動終極執行器 with FinalExecutor() as e: e.call(type_err) e.call(value_err) e.call(attr_err)
執行可以看到方法最終都被執行了,且錯誤資訊一個不漏:
type error value error attr error Traceback (most recent call last): File "<ipython-input-5-1b07c576630b>",line 19,in call func(*args,**kwargs) File "<ipython-input-6-d602d89ed0e7>",line 3,in type_err raise TypeError('x') TypeError: x During handling of the above exception,another exception occurred: Traceback (most recent call last): File "<ipython-input-5-1b07c576630b>",in value_err raise ValueError('x') ValueError: x During handling of the above exception,self.user_ns) File "<ipython-input-6-d602d89ed0e7>",in <module> e.call(attr_err) File "<ipython-input-5-1b07c576630b>",line 15,in __exit__ raise self.last_err File "<ipython-input-5-1b07c576630b>",line 13,in attr_err raise AttributeError('x') AttributeError: x
4. 使用 ExitStack
有了我們的終極執行器,上帝就可以優雅的寫程式碼了。為了讓每個人都能這樣優雅的寫 python 程式碼,python 為我們提供了一個封裝好的功能,當然它的實現要比我們的終極執行器複雜一些(考慮的也更周到一些~)。我們可以通過 contextlib
模組匯入該方法並使用:
from contextlib import ExitStack with ExitStack() as stack: stack.callback(type_err) stack.callback(value_err) stack.callback(attr_err)
注意該 ExitStack
與 FinalExecutor
不同的是,它是倒序執行的。
以上就是python try...finally...的實現方法的詳細內容,更多關於python try...finally的資料請關注我們其它相關文章!