Pythonのコンテキストマネージャって何?って言われると、「withを使ってるあれ」みたいな答え方はできるんですが、以下のような質問は答えられませんでした。というわけで、調べたことでも書きます。
- コンテキストマネージャって、なんのためにあるの?
- コンテキストマネージャって、どうやって実装するの?
- コンテキストマネージャって、何が必要なの?
コンテキストマネージャって何?
公式ドキュメントを見ると以下のように書いています。
3. データモデル — Python 3.6.5 ドキュメント
コンテキストマネージャ(context manager) とは、 with 文の実行時にランタイムコンテキストを定義するオブジェクトです。
なるほど、よくわかりません。with 文の実行時にランタイムコンテキストってなんなの?と思います。
with文ってそもそも何?
with文についてもう少し調べます。 公式ドキュメントでは、withの説明はこちらにあります。
8. 複合文 (compound statement) — Python 3.6.5 ドキュメント
with 文は、ブロックの実行を、コンテキストマネージャによって定義されたメソッドでラップするために使われます (with文とコンテキストマネージャ セクションを参照してください)。
なるほど?とりあえず、コンテキストマネージャを理解するためには、with文を理解する必要があって、with文を理解するためには、コンテキストマネージャを理解する必要があることがわかりました。
これは難しい。
with文の成り立ちを見る
Pythonのドキュメントだけでは、理解することができなかったので、次は、PEPを読みます。
withのPEPがありました。
最初の方に以下の文があります。
This PEP adds a new statement "with" to the Python language to make it possible to factor out standard uses of try/finally statements.
ああ、毎回同じようなtry/finallyを書くのを止めるために、withを導入した、という背景があるんですね。
そして、次にこんな風に書いてあります。
In this PEP, context managers provide
__enter__()
and__exit__()
methods that are invoked on entry to and exit from the body of the with statement.
コンテキストマネージャーは、__enter__()
__exit__()
メソッドを実行するんですね。
なるほど
コンテキストマネージャを雑に実装して見る
とりあえず、なんでも良いので、コンテキストマネージャを実装してみます。
class context_manager_sample: def __enter__(self, *args): print('enter') def __exit__(self, *args): print('exit') with context_manager_sample() as f: print('hello context manager')
これを実行してみます。
$ python3 context_manager_sample.py enter hello context manager exit
なんか動いたようです。
エラーを起こして見る
try/finarlly
に置き換えるものなので、コンテキストマネージャーの中でエラーが起きてもexitは実行されるんでしょう。
やってみます。
class context_manager_sample: def __enter__(self, *args): print('enter') def __exit__(self, *args): print('exit') with context_manager_sample() as f: print('hello context manager') raise Exception('cause error!')
$ python3 context_manager_sample.py enter hello context manager exit Traceback (most recent call last): File "context_manager_sample.py", line 10, in <module> raise Exception('cause error!') Exception: cause error!
確かに、exitが処理されています。 なるほど。
contextlibを使う
Pythonでは、標準ライブラリとして、contextlibが提供されていました。
29.6. contextlib — with 文コンテキスト用ユーティリティ — Python 3.6.5 ドキュメント
先ほどのように、自分で __enter__
__exit__
を実装するのは、悪手のように見えます。
次はこちらを使って同じような処理を実装してみます。
from contextlib import contextmanager @contextmanager def context_manager_sample(): print('enter method') yield print('exit method') with context_manager_sample(): print('hello context manager')
$ python3 contextlib_sample.py enter method hello context manager exit method
ふむ。こちらの方が便利そうですね。
ちょっと待って、asは?
そういえば、withで指定した際のasってどうなってるんだろう、
と思って調べたら、yieldに入ってる値がasに入るらしいですね
from contextlib import contextmanager @contextmanager def context_manager_sample(): print('enter method') yield 'yield value' print('exit method') with context_manager_sample() as sample: print(f'hello context manager: {sample}')
$ python3 contextlib_sample2.py enter method hello context manager: yield value exit method
こういう感じ。
へえー
追記
エキスパートPythonプログラミング改訂2版を読んでると、コンテキストマネージャについて、もう少し詳しく書いてました。