Pythonがコンテキストマネージャを実現する方法


問題
with文を使うために、自分でコンテキストマネージャを実現したいです。
ソリューション
新しいコンテキストマネージャを実現するための最も簡単な方法は、contexlibモジュール中の@contextmanagerを使用することである。コードブロック計時機能を実装したコンテキストマネージャの例である。

import time
from contextlib import contextmanager

@contextmanager
def timethis(label):
  start = time.time()
  try:
    yield
  finally:
    end = time.time()
    print('{}: {}'.format(label, end - start))

# Example use
with timethis('counting'):
  n = 10000000
  while n > 0:
    n -= 1
関数timethis() において、yieldの前のコードはコンテキストマネージャにおいて__enter__() の方法として実行され、yield の後のコードは __exit__() の方法として実行される。異常が発生したら、異常はyield文に投げられます。
以下はより上位のコンテキストマネージャで、リストオブジェクト上のあるトランザクションを実行します。

@contextmanager
def list_transaction(orig_list):
  working = list(orig_list)
  yield working
  orig_list[:] = working
このコードの役割は、リストの変更はすべてのコードの実行が完了し、異常が発生しない場合にのみ有効となります。ここで実証します。

>>> items = [1, 2, 3]
>>> with list_transaction(items) as working:
...   working.append(4)
...   working.append(5)
...
>>> items
[1, 2, 3, 4, 5]
>>> with list_transaction(items) as working:
...   working.append(6)
...   working.append(7)
...   raise RuntimeError('oops')
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: oops
>>> items
[1, 2, 3, 4, 5]
>>>
討論する
通常、コンテキストマネージャを書くには、クラスを定義する必要があります。enter_()ひゅうっとexit_()方法は、以下の通りです。

import time

class timethis:
  def __init__(self, label):
    self.label = label

  def __enter__(self):
    self.start = time.time()

  def __exit__(self, exc_ty, exc_val, exc_tb):
    end = time.time()
    print('{}: {}'.format(self.label, end - self.start))
これも難しいことではないですが、比較的簡単に@contextmanager 注釈の関数を使って書くとちょっと味気ないです。@contextmanagerは、単に含まれるコンテキスト管理関数から書くべきである。もしいくつかのオブジェクト(例えば、ファイル、ネットワーク接続、またはロック)があったら、with のステートメントをサポートする必要があります。 __enter__() の方法と__exit__() の方法を単独で実現する必要があります。
以上がPythonのコンテキストマネージャを実現する方法の詳細です。Pythonのコンテキストマネージャ実現に関する詳細については、他の関連記事に注目してください。