Instead of creating a class to implement the context manager protocol (__enter__ & __exit__), we can use @contextmanager Decorator from contextlib library to decorate any generator function with a single yield statement. This done by wrapping the function in a class that implements the __enter__ and __exit__ methods.
def gen():
# Anything here for the setup
print("setup")
# expression after yield will be returned after calling the gen
# function in `with` block; otherwise, `None` will be returned
# equivalent to `__enter__`
yield
# Anything here will be part of the teardown when we exit the
# `with` block, equivalent to `__exit__`
print("teardown")
with gen():
print("inside `with` block")
print("outside `with` block")
# Output
setutp
inside `with` block
teardown
outside `with` blockHere is how generator functions decorated with contextmanager get evaluated:
- Evaluates the body of the generator function until the
yieldstatement- Invoke the generator function and store the generator object, for example
gen - Calls
next(gen)so the body of generator gets executed untilyieldstatement - Returns the value returned by
yieldso it can used to bound it to the target variable ofwith. Therefore,yieldproduces values we want__enter__to return and evaluation stops
- Invoke the generator function and store the generator object, for example
- Continue evaluating the body of the
withblock - Once we are done from
withblock (exitwithblock), execution continue after theyieldstatement- Checks if there is exception raised in
exc_typeinsidewithblock. If there is exception, then invokegen.throw(exception)in theyieldline inside the generator function - Else, it would invoke
next(gen), which makes the generator to continue execution after theyieldline
- Checks if there is exception raised in