Python's `contextmanager`

Temporarily Tweaking an Object (and Putting It Back)

The problem

Sometimes you need to borrow an object, flip one of its settings for the duration of a helper function, and then hand the object back exactly as you found it—no matter how the function exits (normal return, early return, or raised exception). Doing this by hand with try … finally works, but it clutters the code and is easy to forget.

Enter the context manager

A context manager guarantees “setup → run some code → clean-up” in a single, readable block, whether you use the with statement or the decorator form from contextlib.

A minimal, one-attribute example

from contextlib import contextmanager

@contextmanager
def temporarily(obj, attr, new_value):
    """Temporarily set obj.attr to new_value inside the with-block."""
    old_value = getattr(obj, attr)
    setattr(obj, attr, new_value)
    try:
        yield obj          # code inside the with-block now runs
    finally:
        setattr(obj, attr, old_value)   # always restore

Usage:

class Printer:
    def __init__(self):
        self.verbose = False
    def show(self, msg):
        if self.verbose:
            print(msg.upper())
        else:
            print(msg)

p = Printer()

with temporarily(p, "verbose", True):
    p.show("inside")   # prints INSIDE

p.show("outside")      # prints outside

Changing several attributes at once

When you have more than one setting to flip, you can accept a mapping and loop:

@contextmanager
def temporarily_many(obj, **changes):
    """Temporarily set multiple obj attributes."""
    originals = {name: getattr(obj, name) for name in changes}
    try:
        for name, value in changes.items():
            setattr(obj, name, value)
        yield obj
    finally:
        for name, value in originals.items():
            setattr(obj, name, value)
with temporarily_many(p, verbose=True, prefix="[DBG] "):
    ...

(If the object has scores of attributes, contextlib.ExitStack lets you compose several single-purpose context managers instead of writing yet another helper.)

Decorating a method for effortless rollback

If the attribute juggling belongs inside a method of the object itself, wrap the inner body:

class Printer:
    ...

    @contextmanager
    def loud_mode(self):
        yield from temporarily(self, "verbose", True)

    def log_section(self, messages):
        with self.loud_mode():
            for m in messages:
                self.show(m)

Now every call to log_section guarantees that verbose returns to its former state, even if one of the prints raises.

Class-based context managers (__enter__ / __exit__)

For deeply stateful cases you may want an entire class:

class SwapAttr:
    def __init__(self, obj, attr, new_value):
        self.obj, self.attr, self.new = obj, attr, new_value
    def __enter__(self):
        self.old = getattr(self.obj, self.attr)
        setattr(self.obj, self.attr, self.new)
        return self.obj
    def __exit__(self, exc_type, exc, tb):
        setattr(self.obj, self.attr, self.old)
        # returning False lets any exception propagate

The class form lets you store extra bookkeeping data or share the same manager instance across multiple with blocks.