Cleanup Guarantee in contextmanager

Two context managers are built with @contextmanager. Only one guarantees cleanup when the with block raises:

from contextlib import contextmanager

@contextmanager
def safe_resource(name):
    print(f"open {name}")
    try:
        yield name
    finally:
        print(f"close {name}")

@contextmanager
def unsafe_resource(name):
    print(f"open {name}")
    yield name
    print(f"close {name}")

for factory in (safe_resource, unsafe_resource):
    try:
        with factory("R") as r:
            print("using", r)
            raise ValueError("boom")
    except ValueError:
        print("caught")

What does this print?

A

unsafe_resource raises a RuntimeError from contextlib because a @contextmanager generator must wrap its yield in try/finally, so the second loop iteration crashes before printing anything.

B
open R
using R
close R
caught
open R
using R
caught
C
open R
using R
close R
caught
open R
using R
close R
caught
D
open R
using R
caught
open R
using R
caught