We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Stdlib Mastery step 35 of 55
contextmanager: temporarily patch an attribute, and put it back
Write a context manager that sets an attribute for the duration of a block and restores the previous state on every exit path.
class Holder:
pass
@contextmanager
def temp_attr(obj: object, name: str, value: object) -> Iterator[None]: ...
-
Set
obj.nametovalueon entry. - On exit — normal or exceptional — restore the original value, or delete the attribute if it did not exist beforehand.
- Exceptions from the body must propagate, not be swallowed.
-
It must nest: an inner
temp_attron the same attribute restores the outer block’s value, not the original.
def solve(
initial: dict[str, object], name: str, value: object,
nested_value: object, raise_inside: bool, reuse: bool,
) -> dict[str, object]:
solve builds a Holder, setattrs every entry of initial onto it, then:
-
Enters
temp_attr(obj, name, value)and appendsgetattr(obj, name)toseen. -
If
nested_value is not None, enters a nestedtemp_attr(obj, name, nested_value), appends the value seen inside, and appends the value seen again after the nested block exits. -
If
raise_inside, raisesRuntimeErrorinside the outer block; catches it outside and recordspropagated. -
If
reuse, stores one context-manager object, enters it once successfully, then tries to enter the same object a second time and records whether that was rejected.
Returns:
{"seen": [...], "propagated": bool, "has_after": bool,
"after": object | None, "reuse_rejected": bool}
where after is getattr(obj, name, None).
The production consequence. A generator that yields without try/finally
restores state on the happy path and silently not when the body raises —
which is the exact case the context manager exists for. It passes every test
that does not deliberately raise inside the block, and leaves the attribute
patched for the rest of the process. Use finally, not except: except
forces you to re-raise and is easy to swallow by accident.
Single use. The object returned by calling a @contextmanager function
wraps one generator. Entering it twice fails — on CPython 3.12-3.14 with an
AttributeError, because __enter__ drops the stored constructor arguments
as it runs. The exact type is an implementation detail, so record only that it
was rejected. What is reusable is the function: calling it again builds a
fresh generator, which is also why using it as a decorator is safe.
Typing. The return annotation is Iterator[None], not None. The
generator function returns an iterator; the decorator is what turns that into
a context manager. Annotating the yielded type directly is the most common
mistake in the module and --strict catches it.
For “was there an attribute before?”, None is not a usable sentinel — the
attribute may legitimately hold None. Use a module-level unique object.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.