We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Stdlib Mastery step 39 of 55
nullcontext and suppress: use the stream you were given, close only what you opened
Two small functions that between them cover the whole of contextlib‘s
helper surface.
class FakeStream:
def __init__(self, name: str, log: list[str]) -> None: ...
def read(self) -> str: ... # f"data from {self.name}"
def close(self) -> None: ... # sets self.closed, appends f"close:{name}"
def read_or_default(load: Callable[[], str], default: str) -> str: ...
def process(stream: FakeStream | None, factory: Callable[[], FakeStream]) -> str: ...
-
read_or_defaultreturnsload(), ordefaultif the load reports the file is missing. A permission error must propagate. -
processreturnsstream.read().upper(). WhenstreamisNoneit opens one withfactory()and closes it; when a stream was provided it must not close it.
def solve(mode: str, provided: bool) -> dict[str, object]:
solve builds a load closure that raises FileNotFoundError when
mode == "missing", PermissionError when mode == "denied", and returns
"payload" otherwise. It calls read_or_default(load, "DEFAULT"), catching
PermissionError and recording "PermissionError" as the text. Then it calls
process with FakeStream("given", log) if provided else None, and a
factory producing FakeStream("opened", log). It returns:
{"text": str, "result": str, "log": [...],
"given_closed": bool | None} # None when no stream was provided
Why nullcontext earns its place. The alternative is an if/else with
the body written twice — once for the provided stream, once for the opened
one. Two copies of the same logic, which drift. nullcontext(x) yields x
and does nothing on exit, so the caller’s stream survives the block while the
internally-opened one is closed. That ownership rule is the real content:
closing a handle you did not open is a classic way to break a caller.
Why the exception tuple must be narrow. suppress swallows the exception
and abandons the rest of the block — it is not “ignore and carry on to the
next statement”. And suppress(Exception) around a file read swallows
PermissionError, a full disk, and every bug in the code you called, turning
a misconfigured deployment into a silently empty config. The permission case
here is the test for that.
Typing. nullcontext() reveals nullcontext[None] and nullcontext(5)
reveals nullcontext[int] — the generic is what makes the optional-resource
pattern type-check. Declare the scope as
AbstractContextManager[FakeStream] so both branches of the conditional are
checked against a stated interface rather than an inferred join.
FakeStream has close() but no __enter__, so it needs contextlib.closing
to become a context manager.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.