Python の with 文で一時的に状態を変更する

コンテキストマネージャの便利な使い方の1つは、設定や状態を一時的に変更し、ブロック終了時に自動的に元に戻すことです。これにより、グローバルな設定を安全に一時変更できます。

基本的なパターン

一時的に値を変更して、処理後に復元するパターンです。

from contextlib import contextmanager

config = {"debug": False, "verbose": False}

@contextmanager
def temporary_config(**kwargs):
    old_values = {k: config[k] for k in kwargs}
    config.update(kwargs)
    try:
        yield
    finally:
        config.update(old_values)

print(f"変更前: {config}")  # {'debug': False, 'verbose': False}

with temporary_config(debug=True):
    print(f"変更中: {config}")  # {'debug': True, 'verbose': False}

print(f"変更後: {config}")  # {'debug': False, 'verbose': False}

例外が発生しても、finally で確実に元の値に戻ります。

環境変数の一時変更

環境変数を一時的に変更する例です。

import os
from contextlib import contextmanager

@contextmanager
def temp_environ(**env_vars):
    old_values = {}
    for key, value in env_vars.items():
        old_values[key] = os.environ.get(key)
        os.environ[key] = value
    try:
        yield
    finally:
        for key, old_value in old_values.items():
            if old_value is None:
                del os.environ[key]
            else:
                os.environ[key] = old_value

with temp_environ(DEBUG="1", LOG_LEVEL="DEBUG"):
    print(os.environ.get("DEBUG"))  # 1
    print(os.environ.get("LOG_LEVEL"))  # DEBUG

print(os.environ.get("DEBUG"))  # None(元に戻る)

作業ディレクトリの一時変更

カレントディレクトリを一時的に変更する例です。

import os
from contextlib import contextmanager

@contextmanager
def change_directory(path):
    old_dir = os.getcwd()
    os.chdir(path)
    try:
        yield
    finally:
        os.chdir(old_dir)

print(f"変更前: {os.getcwd()}")

with change_directory("/tmp"):
    print(f"変更中: {os.getcwd()}")

print(f"変更後: {os.getcwd()}")

精度の一時変更(Decimal)

decimal モジュールの精度を一時的に変更する例です。

from decimal import Decimal, getcontext, localcontext

# localcontext() は標準で提供されているコンテキストマネージャ
print(f"デフォルト精度: {getcontext().prec}")  # 28

with localcontext() as ctx:
    ctx.prec = 50
    result = Decimal(1) / Decimal(7)
    print(f"高精度計算: {result}")

# ブロック外では元の精度に戻る
result = Decimal(1) / Decimal(7)
print(f"通常精度: {result}")

属性の一時変更

オブジェクトの属性を一時的に変更するジェネリックなコンテキストマネージャです。

from contextlib import contextmanager

@contextmanager
def temp_attr(obj, attr, value):
    old_value = getattr(obj, attr)
    setattr(obj, attr, value)
    try:
        yield
    finally:
        setattr(obj, attr, old_value)

class Settings:
    timeout = 30

settings = Settings()

with temp_attr(settings, "timeout", 5):
    print(f"変更中: {settings.timeout}")  # 5

print(f"変更後: {settings.timeout}")  # 30

使いどころ

テスト時

本番設定を一時的にテスト用に変更する。モックの設定を一時的に適用する。

特定処理時

一部の処理だけ異なる設定で実行する。デバッグモードを一時的に有効化する。

このパターンを使えば、グローバルな状態を変更しても、スコープを抜けると自動的に元に戻るため、副作用を最小限に抑えられます。