理系学生日記

おまえはいつまで学生気分なのか

コンテキストマネージャ

コンテキストマネージャが導入されて、可読性高くプログラムが書けるようになった。

コンテキストが必要となる場面というのはなかなかに多くて、例えば

  • 特定の機器にログインした後でコマンド実行を行い、ログアウトする
  • ファイルロックをかけて、排他制御をした状態で処理を行い、最後にロックを解除する

というような状況。
こういう状況(コンテキスト)を抽象化したものがコンテキストマネージャが提供する環境になる。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 ...