日々のあれこれφ(..)

もっぱら壁打ち

Python Context Managerを調べた時のメモ

完全に理解できた自信はないのですが、整理のため一旦調べたことをまとめます。

Python 3.8.1

きっかけ

@contextlib.contextmanagerを使ってDBへの接続の処理を実装しているメソッドがありました。この書き方は初めて見たので、今後自分でも必要に応じて使えるようにするために調べました。

コンテキストマネージャーとは

そもそもコンテキストマネージャーって何だという所から始まったのですが、コンテキストマネージャーだけでぐぐるとコンピュータ関係の他の用語としての説明も出てきます。Pythonでも同じ意味で使われているのか判断しかねたのですが、Pythonの場合そういった用語の定義もPEPで確認できました。

PEP0343

このPEPは、__ enter __()メソッドと__exit __()メソッドで構成されるプロトコルを「コンテキスト管理プロトコル」と呼び、そのプロトコルを実装するオブジェクトを「コンテキストマネージャー」と呼ぶことを提案しています。

3. データモデル — Python 3.9.1 ドキュメント

コードブロックを実行するために必要な入り口および出口の処理を扱います。コンテキストマネージャは通常、 with 文( with 文 の章を参照)により起動されますが、これらのメソッドを直接呼び出すことで起動することもできます。

通称with文から起動される。

代表的な使い方としては、様々なグローバル情報の保存および更新、リソースのロックとアンロック、ファイルのオープンとクローズなどが挙げられます。

Python入門などで、ファイルを扱うに必要な以下の処理をwithを使って描くとシンプルに描けると習いましたが、try,finallyで行なっているものをwith文ではコンテキストマネージャーの__ enter __() __exit __()で実現しています。

try:
    f = open('test.txt')
    print(f.read())
finally:
    f.close()
with open('test.txt') as f:
    print(f.read())

open()のようなある処理の前後に何かを行わせるようにしたい処理を実装するにはクラスに__enter__()__exit__()メソッドを使って挙動を定義する必要がありますが、 @contextlib.contextmanagerデコレータを使うとデコレートされたメソッドでこれを実現することができます。

▼普通に実装した時

class MyContextManager:

    def __enter__(self):
        print('ENTER!')
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print('EXIT!')

with MyContextManager() as cm:
    print(cm)
ENTER!
<__main__.MyContextManager object at 0x102f91f70>
EXIT!

▼@contextlib.contextmanagerデコレータを使った時

from contextlib import contextmanager, closing


@contextmanager
def mycontextmanager():
    print('ENTER!')
    yield 'yield'
    print('EXIT!')

with mycontextmanager() as cm:
    print(cm)
ENTER!
yield
EXIT!

@contextmanagerが実装されたクラスは、必ずイテレーターかジェネレーターを返す必要があります。

見ての通り、def mycontextmanager()のyieldが呼ばれたらdef mycontextmanager()の処理は中断され、with文内の処理が走ります。それが終わったら中断されていたdef mycontextmanager()内の処理が再開するといった形です。

おわりに

with open()はよく書いていましたが、内部の処理については調べたことなかったので勉強になりました。