Temporarily Tweaking an Object (and Putting It Back)
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.
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
.
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
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.)
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.
__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.