1. 程式人生 > >使用 contextlib 自動關閉資源

使用 contextlib 自動關閉資源

在Python中, 總有一些需要"善後"的事情要做, 比如說開啟檔案後自動關閉檔案描述符, 比如說想要顯示的釋放某種資源, 比如...

上面這種需求很常見的一個場景就是讀取檔案, 很方便的一種做法是使用with語句來控制

with open('a.txt' 'r') as f:
    f.readline()

這樣寫的好處是, 在with裡邊的程式碼塊執行完畢後, 會自動的關閉關閉檔案. 而且這種寫法可讀性高, 犯錯的機率也會變小.
那麼, 包含with的程式碼塊在執行的時候都做了什麼呢? 它的執行過程又是怎樣的呢?

  1. 首先會計算表示式的值, 返回一個上下文管理器物件
  2. 呼叫上下文管理器物件的__enter__()
    方法
  3. 如果with語句中設定了as目標物件, 將__enter__()返回的返回值賦給目標物件
  4. 執行with中的程式碼塊
  5. 呼叫上下文物件的__exit__()方法, 如果with中程式碼塊中有異常, 那麼會將exception_type, exception_value, traceback傳給__exit__(), 如果其返回False那麼異常會被重新丟擲, 否則異常被掛起, 程式繼續執行

OK, 那麼, 什麼是上下文管理器呢?

上下文管理器是一個物件, 它定義了程式在執行過程中的上下文, 處理程式的執行和退出, 實現了上下文管理協議, 即在物件中定義__enter__()

__exit__()方法
簡單講, 就是實現了__enter__()__exit__()兩個方法的物件, 我們都可以稱之為上下文管理器

例如, 自己實現一個HTML標籤上下文管理器

class HTMLContextManager(object):
    def __init__(self, tag):
        self._tag = tag

    def __enter__(self):
        print('<{}>'.format(self._tag))

    def __exit__(self, exception_type, exception_value, traceback):
        print('</{}>'.format(self._tag))
        return False


with HTMLContextManager('h1'):
    print('I am body')

輸出結果如下

<h1>
I am body
</h1>

可以說是非常的方便了, 在實際的應用當中, 可以把上下文管理器用作鎖的控制, 檔案的開啟關閉, 異常處理等等, 在__enter__()方法中實現資源分配, 預處理工作, 在__exit__()方法中實現資源釋放, 善後工作

但是, 如果想把一個函式物件包裝成一個上下文管理器怎麼辦呢? Python的標準庫提供了更加易用的contextlib上下文管理工具模組, 它可以通過生成器實現, 不需要顯示建立類以及__enter__()__exit__()兩個方法

示例:

@contextmanager
def html_context_manager(tag):
    print('<{}>'.format(tag))
    yield
    print('</{}>'.format(tag))


with html_context_manager('h2'):
    print('I am body')

輸出結果如下

<h2>
I am body
</h2>

比寫一個類更加簡便了! 但是這種寫法, 如果with程式碼塊中的程式碼丟擲了異常, 預設是向上丟擲異常, 程式掛起的, 也就是類中__exit__()方法返回False
還有一種比較好用的用法是把一個類包裝成一個上下文管理器, 即

class MyClass(object):
    def __init__(self):
        pass


@contextmanager
def my_context_manager():
    print('before call')
    yield MyClass()
    print('after call')

未經允許禁止轉載 https://spxcds.com/2018/12/17/python_contextlib