高校生物549842 views
いろは2986023 views
Computer365120 views
世界の国560595 views
中学数学621382 views
りんご192546 views
高校化学2913383 views
高校日本史189857 views
教育148875 views
中学英語808712 views
Help
Tools

English

Python の contextlib.contextmanager でコンテキストマネージャを作る

contextlib.contextmanager デコレータを使うと、クラスを定義せずにジェネレータ関数からコンテキストマネージャを作成できます。シンプルなコンテキストマネージャを手軽に作りたい場合に便利です。

基本的な使い方

@contextmanager デコレータを付けたジェネレータ関数を定義します。

from contextlib import contextmanager

@contextmanager
def my_context():
    print("開始")
    yield
    print("終了")

with my_context():
    print("処理中")

# 出力:
# 開始
# 処理中
# 終了

yield の前が __enter__() に、後が __exit__() に相当します。

yield で値を返す

yield で値を返すと、as で受け取れます。

from contextlib import contextmanager

@contextmanager
def open_file(filename, mode):
    f = open(filename, mode)
    try:
        yield f
    finally:
        f.close()

with open_file("data.txt", "w") as f:
    f.write("Hello")

try-finally パターン

例外が発生してもクリーンアップを確実に行うには、try-finally を使います。

from contextlib import contextmanager

@contextmanager
def managed_resource():
    print("リソース確保")
    resource = {"data": []}
    try:
        yield resource
    finally:
        print("リソース解放")
        resource["data"].clear()

with managed_resource() as r:
    r["data"].append(1)
    raise ValueError("エラー")  # finally は実行される

クラスベースとの比較

クラスベース

状態管理が複雑な場合に適している。__enter____exit__ を明示的に定義。コードが長くなりがち。

@contextmanager

シンプルなケースに適している。ジェネレータで直感的に書ける。コードが簡潔。

同じ機能をクラスで書くと以下のようになります。

# クラスベース
class Timer:
    def __enter__(self):
        self.start = time.time()
        return self
    
    def __exit__(self, *args):
        print(f"経過: {time.time() - self.start:.2f}秒")

# @contextmanager
@contextmanager
def timer():
    start = time.time()
    yield
    print(f"経過: {time.time() - start:.2f}秒")

例外の処理

try-except で例外をキャッチして処理することもできます。

from contextlib import contextmanager

@contextmanager
def suppress_value_error():
    try:
        yield
    except ValueError as e:
        print(f"ValueError を抑制: {e}")

with suppress_value_error():
    raise ValueError("テスト")
print("処理継続")
# ValueError を抑制: テスト
# 処理継続

例外をキャッチしなければ、そのまま外部に伝播します。

引数を受け取る

通常の関数と同様に引数を受け取れます。

from contextlib import contextmanager

@contextmanager
def indent(level):
    prefix = "  " * level
    print(f"{prefix}開始")
    yield prefix
    print(f"{prefix}終了")

with indent(2) as prefix:
    print(f"{prefix}処理中")

# 出力:
#     開始
#     処理中
#     終了

@contextmanager を使えば、数行でコンテキストマネージャを作成できます。シンプルなリソース管理や状態の一時変更に最適です。