クラス

【Python】コンテキストマネージャーの基本 ~ with...as命令にオブジェクトを渡すには ~

【Python】コンテキストマネージャーの基本 _ with...as命令にオブジェクトを渡すには _

Pythonでは、ファイル操作等のリソースのクリーンアップの処理のためによくwith...as命令を使用します。このwith...as命令に渡せるオブジェクトはコンテキストマネージャーに対応しているかどうかで決まります。本記事では、コンテキストマネージャーの基本について解説します。

コンテキストマネージャの基本

with...as命令

Pythonでファイルをオープンする場合には、クローズのし忘れを起こさないようにwith...as命令をよく用います。

例えば、非常に簡単な例としては以下のようなサンプルが考えられます。以下の例では"サンプルファイル"という文字列が書かれている'sample.txt'を読み込み専用で読み込み表示している例です。

with open('sample.txt', 'r', encoding='UTF-8') as file:
    data = file.read()

print(data)
【実行結果】
サンプルファイル

利用したリソースのクリーンアップをあらかじめ用意してくれているためファイルのクローズ忘れを防止してくれます。

pythonではtry...finally命令で記載する方法もありますが、シンプルでありかつ明瞭なコードとして表現できるため、with...as命令がよく利用されます。

with...as命令にオブジェクトを渡せるかどうかはコンテキストマネージャーに対応しているかどうかによって決まってきます。以降で、コンテキストマネージャーを自分で実装する場合に必要となる条件などの基本について説明していきます。

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

コンテキストマネージャーであることの条件は、実装するクラスが以下のメソッドを実装しているかどうかです。具体的には、__enter__メソッドと__exit__メソッドが実装されている必要があります。

実装するメソッド概要
__enter__(self)with句に入った時に呼び出されるメソッド。リソースを準備し、戻り値をasで指定された変数に反映する。
__exit__(self, exception_type, exception_value, traceback)with句を抜けるときに呼び出されるメソッド。exception_type, exception_value, tracebackはwith句内で例外発生した場合に、例外の型、値、トレースバックが渡される。

__enter__メソッドは、with句に入った時に呼び出されるメソッドです。returnで戻す戻り値がwith...asのasで指定した変数に反映されます。

__exit__メソッドは、with句を抜けるときに呼び出されるメソッドです。引数として受け取るexception_type, exception_value, tracebackは、with句内で例外が発生した場合に、例外の型や値、トレースバック情報が渡されます。

簡単な例として、コンテキストマネージャーの条件を満たしたSampleContextというクラスを作成した例を紹介します。

class SampleContext:
    # コンテキストを作成する
    def __enter__(self):
        print('--- コンテキスト開始(enter) ---')
        return self

    # コンテキストを終了する
    def __exit__(self, exception_type, exception_value, traceback):
        print('--- コンテキスト終了(exit) ---')
        if exception_type is None:
            print('エラーなし')
        else:
            print(f'エラー: {exception_value}')
            return True

    # 任意の関数
    @staticmethod
    def test():
        print('testメソッド')


def main():
    with SampleContext() as sample:
        sample.test()
        # # 何かしらエラーが発生すると挙動が変わる
        # raise ValueError('不正な値')


if __name__ == '__main__':
    main()
【実行結果】
--- コンテキスト開始(enter) ---
testメソッド
--- コンテキスト終了(exit) ---
エラーなし

SampleContextというクラスを作成し、main()の中のwith句内で呼び出しを行っています。with句に入った際に「--- コンテキスト開始(enter) ---」という文字列が表示されていることから__enter__メソッドが呼び出されていることが分かります。

その後、sample.test()でSampleContext内で定義したメソッドが呼び出せていることから、asで指定したsampleにreturnで指定されたクラスのオブジェクトが設定されています。

最後に処理を抜ける際には「--- コンテキスト終了(exit) ---」という文字列が表示されていることから__exit__メソッドが呼び出されています。

また、with句内では例外は発生していないため、exception_typeには何も設定されていないことから「エラーなし」と表示されます。

では、with句内で例外が発生した場合はどのようになるでしょうか。main()内の記載を以下のように変更して実行してみてください。

def main():
    with SampleContext() as sample:
        # sample.test()
        # 何かしらエラーが発生すると挙動が変わる
        raise ValueError('不正な値')
【実行結果例】
--- コンテキスト開始(enter) ---
--- コンテキスト終了(exit) ---
エラー: 不正な値

この例では、明示的にraiseでValueErrorを発生させています。その結果、__exit__メソッドでは、例外情報が入力されて挙動が変わっています。

この例の__exit__メソッドは返却値としてTrueを返却しています。これは、例外をコンテキストマネージャー内で処理済みであることを意味しています。

以下のように、return Falseとするか、もしくは返却値を指定しない場合には、最後にトレースバックが表示されます。

    # コンテキストを終了する
    def __exit__(self, exception_type, exception_value, traceback):
        print('--- コンテキスト終了(exit) ---')
        if exception_type is None:
            print('エラーなし')
        else:
            print(f'エラー: {exception_value}')
            return False
【実行結果】
--- コンテキスト開始(enter) ---
--- コンテキスト終了(exit) ---
エラー: 不正な値
Traceback (most recent call last):
  File "D:\sample\context_manager_basic.py", line 38, in <module>
    main()
  File "D:\sample\context_manager_basic.py", line 34, in main
    raise ValueError('不正な値')
ValueError: 不正な値

この場合、上位のexceptで受け取って処理をすることも可能です。

上記で見てきたように、コンテキストマネージャーの条件を満たすように__enter__メソッドと__exit__メソッドをクラスに記載することで、with..as命令で使用できるクラスを定義することが可能です。