python 中 try...finally... 的優雅實現

1. 關於 try.. finally..

假如上帝用 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 美學,咱們能夠對這段進行優化,使它看起來更爲美觀。測試

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

執行結果爲: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__Nonecode

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

咱們的終極執行器使用示例爲: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

4. 使用 ExitStack

有了咱們的終極執行器,上帝就能夠優雅的寫代碼了。爲了讓每一個人都能這樣優雅的寫 python 代碼,python 爲咱們提供了一個封裝好的功能,固然它的實現要比咱們的終極執行器複雜一些(考慮的也更周到一些~)。咱們能夠經過 contextlib 模塊導入該方法並使用:it

from contextlib import ExitStack


with ExitStack() as stack:
    stack.callback(type_err)
    stack.callback(value_err)
    stack.callback(attr_err)

注意該 ExitStackFinalExecutor 不一樣的是,它是倒序執行的。


Over.

相關文章
相關標籤/搜索