クラス

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

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

Pythonのコンテキストマネージャーの基本と実装方法について解説します。

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

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

本記事では、コンテキストマネージャーの基本と実装方法について紹介します。

with…as命令

コンテキストマネージャーの話の前にまずはwith…as命令についておさらいです。

例えば、Pythonでファイルをオープンする場合には、クローズ忘れを起こさないようにwith…as命令をよく用います。非常に簡単な例としては以下のようなサンプルが考えられます。以下は、”サンプルファイル”という文字列が書かれているsample.txtを読み込み専用(“r”)で読み込み表示している例です。

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

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

上記のようにwith…as命令を使用すると、ファイルのクローズ忘れを防止してくれます。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の戻り値が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_type}, {exception_value}, {traceback}")
            return True

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


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

    print("後続処理")


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

上記の例では、SampleContextという独自のクラスを作成し、main()の中のwith句内で呼び出しを行っています。

with句に入った際に「— コンテキスト開始(enter) —」という文字列が表示されていることから__enter__メソッドが呼び出されていることが分かります。

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

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

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

with句で例外が発生した場合の挙動

では、with句内で例外が発生した場合はどのようになるでしょうか。上記で紹介したプログラムのmain()内でコメントアウトされている部分を以下のように変更して実行してみてください。

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

    print("後続処理")
【実行結果例】
--- コンテキスト開始(enter) ---
testメソッド
--- コンテキスト終了(exit) ---
エラー: <class 'ValueError'>, 不正な値, <traceback object at 0x000001BCFFC92440>
後続処理

この例では、明示的に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_type}, {exception_value}, {traceback}")
            return False
【実行結果】
--- コンテキスト開始(enter) ---
testメソッド
--- コンテキスト終了(exit) ---
エラー: <class 'ValueError'>, 不正な値, <traceback object at 0x0000026568C82440>
Traceback (most recent call last):
...省略...
    raise ValueError('不正な値')
ValueError: 不正な値

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

まとめ

Pythonのコンテキストマネージャーの基本と実装方法について解説しました。

コンテキストマネージャーであるには、実装するクラスに__enter__メソッドと__exit__メソッドが実装されている必要があります。本記事では、独自にコンテキストマネージャーに対応したクラスを作成する方法を紹介しました。

with…as命令で、使用可能なクラスを定義する場合の参考にしてもらえればと思います。