We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Everyday Standard Library Tools step 41 of 55
Open a List of Resources and Close Them All
Write open_all(names, opener), a context manager that opens one resource
per name and closes them all when the caller's with block ends. If one
open fails, it closes the ones it already opened.
with open_all(["a", "b"], opener) as handles:
... # handles holds two open handles, a then b
# both are closed here, b first
Handle in the starter is a Protocol. Any object with a name attribute
and a close() method counts as a Handle, whatever its class.
What to write
Fix open_all in the starter. It is a generator function decorated with
@contextmanager.
- Call
opener(name)once per name, in input order. - Register each handle with an
ExitStackas soon asopenerreturns it, before you open the next one. - Yield the list of handles only after every open has succeeded.
- If
openerraises, close the handles already opened, newest first, and let the error reach the caller. Do not run the caller's block. - When the caller's block ends, normally or with an exception, close every handle in reverse order.
FakeFile has close() but is not a context manager, so wrap each one in
contextlib.closing before you pass it to stack.enter_context. Keep the
yield inside the with ExitStack() block, so the stack closes the handles
when the caller's block ends.
The starter has two bugs. It closes the handles in input order instead of reverse order, and it closes nothing when an open or the caller's block raises.
What solve reports
The starter already has solve(names, fail_on). Keep it as it is. Its opener
records "open:<name>" and returns a FakeFile. When the name equals
fail_on, the opener records "error:<name>" and raises OSError(name)
instead. Each FakeFile records "close:<name>" when it closes. Inside the
with block, solve records "body". It returns:
log: every open, error, body and close event, in order.opened: the handle names the block received, or[]if an open failed.error:"OSError:<name>"if an open failed, otherwise"".
For names=["a", "b", "c"] with no failure, the log is:
["open:a", "open:b", "open:c", "body", "close:c", "close:b", "close:a"]
Edge cases
- With
fail_on="b", the log is["open:a", "error:b", "close:a"],openedis[]anderroris"OSError:b".open_allnever calls the opener for the names afterb. - An empty
nameslist still runs the block. The result is{"log": ["body"], "opened": [], "error": ""}. - The tests pass
fail_on=""when no open should fail. - No test raises inside the caller's block, but
open_allmust still close every handle when that happens.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.