2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

withとは

最初にwithの具体的な例を挙げます。 たとえば、ファイルを開けたら責任をもってcloseしないといけません。

withを使わない場合の例

f = open("example.txt", "r")

print(f.read())

# 自分で閉じないといけない
f.close()

withを使う場合の例

# withを使うとブロックを抜けたときに閉じてくれる
with open("example.txt", "r") as f:
    print(f.read())

このように、ブロックに入るとき、出るときに特定の処理を自動的に実行させる方法を解説します。

実際に作ってみる

公式ドキュメントを参考にしています。

クラスの場合

関数と比べて可読性が良く、分かりやすいです。

class Context:
    def __enter__(self):
        print("enter")
        return self  # 必要に応じてオブジェクトを返す

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")


if __name__ == "__main__":
    with Context():
        print("body")

実行結果

enter
body
exit

解説

__enter__メソッドはブロックに入る前に実行される処理を定義します。
__exit__メソッドはwithブロックを抜けたときの処理を定義します。

__exit__メソッドの引数について

__exit__メソッドは以下の3つの引数を受け取ります:

  • exc_type: 発生した例外の型(例外がなければNone)
  • exc_val: 例外のインスタンス(例外がなければNone)
  • exc_tb: トレースバック情報(例外がなければNone)

これらの引数を活用して例外処理を行うことができます:

class SafeContext:
    def __enter__(self):
        print("リソースを取得します")
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            print(f"例外が発生しました: {exc_type.__name__} - {exc_val}")
            # 例外を処理する場合はTrueを返す(例外が呼び出し元に伝播しなくなる)
            return True
        print("正常終了:リソースを解放します")
        return False  # 例外があれば再送出(デフォルト)
        
if __name__ == "__main__":
    # 正常処理の場合
    with SafeContext():
        print("正常処理")
        
    # 例外発生の場合
    with SafeContext():
        print("例外を発生させます")
        raise ValueError("テスト例外")
    
    print("プログラムは続行されます")  # 例外が処理されたので実行される

with文の進行について 

引用:以下リンク

一つの "要素" を持つ with 文の実行は以下のように進行します:

コンテキスト式 (with_item で与えられた式) を評価することで、コンテキストマネージャを取得します。

コンテキストマネージャの enter() メソッドが、後で使うためにロードされます。

コンテキストマネージャの exit() メソッドが、後で使うためにロードされます。

コンテキストマネージャの enter() メソッドが呼ばれます。

with 文にターゲットが含まれていたら、それに enter() からの戻り値が代入されます。

asを使うには

class FileContext:
    def __init__(self, file_name: str, mode):
        self.f = open(file_name, mode)

    def __enter__(self):
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
        # 例外が発生した場合のログ記録などもここで行える
        if exc_type:
            print(f"ファイル操作中に例外が発生: {exc_val}")


if __name__ == "__main__":
    with FileContext("example.txt", "r") as f:
        print(f.read())

__enter__メソッドの戻り値がasで指定した変数に代入されます。これにより、withブロック内でその値を使用できます。

実用例として考えられるのは:

  • データベース接続の自動クローズ
  • ファイルの自動クローズ
  • スレッドロックの自動解放
  • 一時的な設定変更と元に戻す処理
  • 計測処理(処理時間の計測など)

関数の場合

クラスを使う場合と比べて、少し面倒だと感じます。

最低限必要なコード

最低限tryfinallyそしてyieldは実装する必要があります。

from contextlib import contextmanager


# デコレーターを使う
@contextmanager
def WithContext():
    print("enter")  # 前処理
    try:
        yield  # withブロック内の処理へ制御を移す
    finally:
        print("exit")  # 後処理(必ず実行される)


if __name__ == "__main__":
    with WithContext():
        print("body")

実行結果

try
Context
finally

関数で値を返す場合

from contextlib import contextmanager


@contextmanager
def file_opener(filename, mode="r"):
    try:
        f = open(filename, mode)
        yield f  # yieldした値がasの後ろの変数に代入される
    except Exception as e:
        print(f"ファイル操作中にエラーが発生: {e}")
        raise  # 例外を再送出
    finally:
        print("ファイルを閉じます")
        f.close()  # 例外があってもなくても必ず実行


if __name__ == "__main__":
    with file_opener("example.txt") as f:
        print(f.read())

yieldを使うことで、withブロック内へ値を渡し、ブロックの実行が終わったら処理を再開します。例外処理もtry-except-finally構文で自然に書けます。

コンテキストマネージャの内部動作

@contextmanagerデコレータはジェネレータ関数をコンテキストマネージャに変換します:

  1. withブロックに入ると、ジェネレータが実行されyieldまで処理が進みます
  2. yieldの値がwithブロックに渡されます(as変数に代入)
  3. withブロック内の処理が実行されます
  4. withブロックを抜けると、ジェネレータ内のyield以降の処理が再開されます
  5. 例外が発生した場合はexceptブロックでキャッチできます
  6. finallyブロックは必ず実行されます。
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?