クラス

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

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

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

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

Python では、ファイル操作などのリソースのクリーンアップ処理のためによく with ... as ... を使用します。with に渡すオブジェクトはコンテキストマネージャーに対応している必要があります

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

with ... as ...

コンテキストマネージャーの話の前に、with ... as ... について説明します。例として、Python でファイルの入出力を考えてみます。

ファイルを操作する場合には、処理が終わったらクローズ処理が必要です。クローズ処理は忘れてしまうことが多いのですが、以下のように with ... as ... を使用することでクローズ忘れを防止できます。

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

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

例のように with ... as ... を使用すると読み込みの read の後にファイルをクローズします。これにより、ファイルのクローズ忘れを防止できます。Python では、try ... finally ... でも記載できますが、シンプルかつ明瞭なコードとして表現できるため、with ... as ... が利用されます。

with ... as ... にオブジェクトを渡せるかどうかはコンテキストマネージャーに対応しているかどうかで決まります。上記で紹介したファイル入出力で with ... as ... が使用できるのはコンテキストマネージャーに対応しているためです。

なお、コンテキストマネージャーは自分で実装することが可能です。以降では、コンテキストマネージャーを自分で実装する際の基本を説明していきます。

コンテキストマネージャーの実装

コンテキストマネージャーであるためには、対象のクラスが「__enter__」と「__exit__」というメソッドを実装している必要があります。

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

__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 内で例外が発生した場合について、以下で確認しましょう。

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

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

例では、明示的に raiseValueError を発生させています。その結果 __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 ... で使用可能なクラスを定義する際の参考にしてください。

ソースコード

上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

あわせて読みたい
【Python Tech】プログラミングガイド
【Python Tech】プログラミングガイド
ABOUT ME
ホッシー
ホッシー
システムエンジニア
はじめまして。当サイトをご覧いただきありがとうございます。 私は製造業のメーカーで、DX推進や業務システムの設計・開発・導入を担当しているシステムエンジニアです。これまでに転職も経験しており、以前は大手電機メーカーでシステム開発に携わっていました。

プログラミング言語はこれまでC、C++、JAVA等を扱ってきましたが、最近では特に機械学習等の分析でも注目されているPythonについてとても興味をもって取り組んでいます。これまでの経験をもとに、Pythonに興味を持つ方のお役に立てるような情報を発信していきたいと思います。どうぞよろしくお願いいたします。

※キャラクターデザイン:ゼイルン様
記事URLをコピーしました