假如上帝用 python 爲每個來到世界的生物編寫程序,那麼除去中間過程的種種複雜實現,最不可避免的就是要保證每一個實例最後都要掛掉。代碼可簡寫以下: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
方法出現錯誤,那麼就不能正常去見上帝了。爲了保證每一個生物(無論有沒有掛掉)都能見到上帝他老人家,並開始下一個輪迴(無論有沒有見到),須要作以下處理:shell
... finally: try: go_die() finally: try: come_to_see_me() finally: reincarnate()
OK,功能雖然實現了,但按照 The Zen of Python
所說:Flat is better than nested.
(扁平優於嵌套),那麼這段代碼就略顯醜陋了。爲了遵循 python 美學,咱們能夠對這段進行優化,使它看起來更爲美觀。測試
__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()
執行結果爲:this
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
。code
有了 __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
咱們的終極執行器使用示例爲:ip
# 定義 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)
運行能夠看到方法最終都被執行了,且錯誤信息一個不漏:input
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>", line 19, in call func(*args, **kwargs) File "<ipython-input-6-d602d89ed0e7>", line 8, in value_err raise ValueError('x') ValueError: x 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-6-d602d89ed0e7>", line 19, 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 19, in call func(*args, **kwargs) File "<ipython-input-6-d602d89ed0e7>", line 13, in attr_err raise AttributeError('x') AttributeError: x
有了咱們的終極執行器,上帝就能夠優雅的寫代碼了。爲了讓每一個人都能這樣優雅的寫 python 代碼,python 爲咱們提供了一個封裝好的功能,固然它的實現要比咱們的終極執行器複雜一些(考慮的也更周到一些~)。咱們能夠經過 contextlib
模塊導入該方法並使用:it
from contextlib import ExitStack with ExitStack() as stack: stack.callback(type_err) stack.callback(value_err) stack.callback(attr_err)
注意該 ExitStack
與 FinalExecutor
不一樣的是,它是倒序執行的。
Over.