Batu Lab NotesPractical developer guides

Use a context manager to restore a module-level setting

By Batu ยท English technical notes

Also published in our primary archive.

Use a context manager to restore a module-level setting

To temporarily change a module-level setting and reliably put it back, make the context manager own both the change and the restoration. Its finally clause runs when the with body completes normally and when it exits by raising an exception. This is the right boundary: code inside the block may use mode = "lenient", but it should not be responsible for remembering cleanup.

The first function below is the failure case. It changes mode, calls code that raises, and never reaches its restoration assignment. The corrected temporary_mode context manager saves the old value before the change, yields control to the caller, and restores that saved value in finally. The example exercises both branches: a successful block and a block that raises ValueError. In each case, the event log and assertions show that the observable module setting ends as "strict".

This pattern restores the value it captured; it does not make concurrent writes to the same global variable safe or establish a general transaction. If other code mutates the setting while the block is active, a simple save-and-restore policy may overwrite that change. Prefer passing configuration explicitly when shared mutable state is avoidable.

contextlib.contextmanager is available in Python 3.2 and later. The try/finally behavior used here is part of Python's exception-handling model. See the contextlib documentation and the Python tutorial on errors and exceptions.

AI assistance disclosure: This article was drafted with AI assistance and checked using the synthetic example shown below.

from contextlib import contextmanager

mode = "strict"
events = []


def failing_change():
    global mode
    mode = "lenient"
    raise ValueError("bad record")


@contextmanager
def temporary_mode(value):
    global mode
    previous = mode
    mode = value
    events.append(f"entered:{mode}")
    try:
        yield
    finally:
        mode = previous
        events.append(f"restored:{mode}")


try:
    failing_change()
except ValueError:
    events.append(f"failed-without-cleanup:{mode}")

mode = "strict"
with temporary_mode("lenient"):
    events.append(f"success-body:{mode}")
assert mode == "strict"

try:
    with temporary_mode("lenient"):
        events.append(f"failure-body:{mode}")
        raise ValueError("bad record")
except ValueError:
    events.append(f"caught:{mode}")

assert mode == "strict"
for event in events:
    print(event)
failed-without-cleanup:lenient
entered:lenient
success-body:lenient
restored:strict
entered:lenient
failure-body:lenient
restored:strict
caught:strict