2.24. コンテキストマネージャ¶
with 文は、セットアップコード、続いて本体、そして後始末コードを実行します -- そして本体が途中で失敗した場合でも、後始末が確実に実行されることを保証します。このセットアップと後始末を提供するメソッドの組は コンテキストマネージャ と呼ばれます。
その形は次のとおりです。
with <expression> as <name>:
<body>
この式はコンテキストマネージャを返します。Python はその __enter__ メソッドを呼び出し、必要に応じて結果を <name> に束縛し、本体を実行してから __exit__ を呼び出します -- 本体が正常に完了したか例外を送出したかにかかわらずです。
__exit__ は、ブロック内で何が起ころうとも、ブロックの終わりに実行されます。¶
2.24.1. コンテキストマネージャを使う¶
典型的な例はファイルを開くことです。
with open("data.txt") as f:
text = f.read()
# f is now closed, even if read() failed
open() は、それ自体がコンテキストマネージャであるファイルオブジェクトを返します。__enter__ はファイルを返し、__exit__ はそれを閉じます。with ブロックは、「終わったら必ずファイルを閉じる」ことを、呼び出し側が覚えておかなければならないものではなく、デフォルトの動作にします。
2.24.1.1. 複数のコンテキストマネージャ¶
1 つの with 文で、カンマで区切って複数のコンテキストマネージャを同時に入ることができます。
with open("input.txt") as src, open("output.txt", "w") as dst:
dst.write(src.read())
これは入れ子になった 2 つの with ブロックと同等ですが、よりフラットです。マネージャは左から右へ入り、逆の順序で出ます。右側のマネージャの __enter__ が例外を送出した場合でも、左側のマネージャの __exit__ は実行されます。
2.24.2. コンテキストマネージャを書く¶
__enter__ メソッドと __exit__ メソッドを持つクラスはどれでもコンテキストマネージャとして機能します。
class Section:
def __init__(self, label):
self.label = label
def __enter__(self):
print("---", self.label, "---")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("--- end", self.label, "---")
return False
with Section("setup"):
print("doing the work")
出力:
--- setup ---
doing the work
--- end setup ---
__exit__ は、ブロックを終了させた例外を記述する 3 つの引数を受け取ります。ブロックが正常に完了した場合は 3 つの None 値を受け取ります。False(または None)を返すと、後始末の後に任意の例外がそのまま伝播します。True を返すと、例外を握りつぶします。
ファイルだけでなく、「開く / 閉じる」や「獲得 / 解放」のライフサイクルを持つあらゆるリソースにコンテキストマネージャを使用してください。このパターンは、後始末をセットアップと対にして、両方が導入される地点に置いておくため、長い関数の途中で閉じ忘れてもリソースが漏れることはありません。