Python標準ライブラリのcontextlib.ExitStackクラスを使うと複数のコンテキストマネージャ1をまとめて管理できます。ExitStackクラスのメリットを以下に列挙します。
- ExitStackクラスもコンテキストマネージャなのでwith文でスコープを指定できる。
- クラスのインスタンス変数に含まれるコンテキストマネージャを
__init__
メソッドで登録してまとめて管理できる。 -
push
メソッドとenter_context
メソッドの使い分けで__enter__
メソッドの呼び出しを制御できる。
push
メソッドとenter_context
メソッドの動作確認
push
メソッドは管理対象の__enter__
メソッドを呼び出さず、enter_context
メソッドは管理対象の__enter__
メソッドを呼び出します。通常はenter_context
メソッドを使用すれば問題ありません。
from contextlib import ExitStack
class TestClass:
def __init__(self):
print("TestClass.__init__()")
def __enter__(self):
print("TestClass.__enter__()")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("TestClass.__exit__()")
with ExitStack() as stack:
stack.push(TestClass())
# TestClass.__init__()
# TestClass.__exit__()
with ExitStack() as stack:
stack.enter_context(TestClass())
# TestClass.__init__()
# TestClass.__enter__()
# TestClass.__exit__()
実用例:ディレクトリ中のファイルをopen
組み込み関数でまとめて開いてExitStack
で管理する。
ハンドルリソース管理の問題から多用することはありませんが、ExitStack
とwith
文を組み合わせることでディレクトリ中のファイルをまとめて開いて管理できます。
import os
from contextlib import ExitStack
from typing import List, BinaryIO
# ExitStackオブジェクトをwith文で作成・開始する。
with ExitStack() as stack:
# カレントディレクトリ中のファイルのパスを取得する。
# open組み込み関数が失敗するのでディレクトリは除外する。
paths: List[str] = [path for path in os.listdir(".") if os.path.isfile(path)]
# ファイルをExitStackに追加しながら開く。
files: List[BinaryIO] = [stack.enter_context(open(path, "rb")) for path in paths]
# 開いたファイルを適当に処理する。
file: BinaryIO
for file in files:
print(",".join([hex(byte).ljust(2, "0") for byte in file.read(5)]))
# ExitStackオブジェクト(stack)に登録されたコンテキストマネージャは
# with文の終了時点で解放される。
関連情報
-
contextlib.AsyncExitStack
クラスで非同期コンテキストマネージャに対する解放処理を使用できます。