Yes, the context manager is kept active. yield pauses the function, nothing is exited.
yield doesn't alter the order of execution inside a function. The function is merely 'paused', frozen at the point where the yield expression has executed and produced a value. When the generator is later on resumed (by calling __next__ on the iterator), the function continues at that point again. A with statement __exit__ method can't be called when a generator is paused, so the context manager can't be exited until the generator is resumed, anyway.
You can see this happen if you create a simple context manager with the @contextmanager decorator (itself relying on a generator for the implementation!):
import sys
from contextlib import contextmanager
@contextmanager
def loud_contextmanager():
print("Context entered!")
try:
yield "value to bind the 'as' target to"
finally:
exc_info = sys.exc_info()
if exc_info:
print("Context exited with an exception!", exc_info)
else:
print("Context exited!")
def generator_stages():
yield "First yield, outside of a context manage"
with loud_contextmanager() as cm_as_value:
yield f"Inside of the with block, received {cm_as_value}"
yield "Outside of with block, last yield"
And when you pull values from the generator to print, you'll see this:
>>> gen = generator_stages()
>>> print(next(gen))
First yield, outside of a context manage
>>> print(next(gen))
Context entered!
Inside of the with block, received value to bind the 'as' target to
>>> print(next(gen))
Context exited with an exception! (None, None, None)
Outside of with block, last yield
>>> next(gen, "generator was done")
'generator was done'
Note that the context is not exited until we retrieve the 3rd value! After the second next() call the code is paused at a point inside of the with block, and only when unpaused can the context be exited, and the finally suite of the loud_contextmanager() function can be run.