コンテキストマネージャが導入されて、可読性高くプログラムが書けるようになった。
コンテキストが必要となる場面というのはなかなかに多くて、例えば
- 特定の機器にログインした後でコマンド実行を行い、ログアウトする
- ファイルロックをかけて、排他制御をした状態で処理を行い、最後にロックを解除する
というような状況。
こういう状況(コンテキスト)を抽象化したものがコンテキストマネージャが提供する環境になる。Python ではこれを with 文で以下のように書ける。
with LoginContext() hoge() # 「ログインした状態」の処理 with locking_context(): fuga() # 「排他制御をした状態」での処理
たいへんに分かりやすい。いかにコンテキストマネージャが可読性を上げるかが分かると思う。
あれ、ログイン処理やログアウト処理どこに行った
with LoginContext()
hoge()
という記述の中にログアウト処理がでてこない、詐欺だ、という感じもするが、コンテキストマネージャはそのコンテキストに入るときの処理と、コンテキストを抜けるときの処理を、それぞれ __enter__(self) と __exit__(self, type, value, traceback) というメソッド内に定義する。
たとえばログインコンテキストであれば、
class LoginContext: def __enter__(self): login() def __exit__(self, type, value, traceback): if traceback is None: # 正常系 logout() else: # 異常系 fugafuga()
というかんじで実装することになる。
メインの処理の可読性は上がったけれど、コンテキストマネージャの可読性は低い。特に異常系の可読性が低い。この状況は悲しいのだけれど、これをうまい具合に隠蔽してくれるのが contextlib。正常系だけを考えると以下のようになって非常にシンプル。
from contextlib import contextmanager @contextmanager def logincontext(): login() yield logout() with logincontext(): hoge()
hoge の中で例外が発生したときもログアウトしたいのであれば、
@contextmanager def logincontext(): login() try: yield except Exception as err: print(err) finally: logout()
とでも書いておけば良い。
from contextlib import contextmanager @contextmanager def logincontext(): login() try: yield finally: logout() def login(): print("login ...") def logout(): print("logout ...") def hoge(): raise Exception("exception in hoge") if __name__ == "__main__": with logincontext(): hoge() # login ... # exception in hoge # logout ...