yield expression returns control to the whatever is using the generator. The generator pauses at this point, which means that the @contextmanager decorator knows that the code is done with the setup part.
In other words, everything you want to do in the context manager __enter__ phase has to take place before the yield.
Once your context exits (so the block under the with statement is done), the @contextmanager decorator is called for the __exit__ part of the context manager protocol and will do one of two things:
-
If there was no exception, it’ll resume your generator. So your generator unpauses at the
yieldline, and you enter the cleanup phase, the part -
If there was an exception, the decorator uses
generator.throw()to raise that exception in the generator. It’ll be as if theyieldline caused that exception. Because you have afinallyclause, it’ll be executed before your generator exits because of the exception.
So, in your specific example the sequence is as follows:
-
with time_print("processes"):This creates the context manager and calls
__enter__on that. -
The generator starts execution,
t = time.time()is run. -
The
yieldexpression pauses the generator, control goes back to the decorator. This takes whatever was yielded and returns that to thewithstatement, in case there is anas targetpart. HereNoneis yielded (there is only a plainyieldexpression). -
[doproc() for _ in range(500)]is run and completes. -
The context manager
__exit__method is run, no exception is passed in. -
The decorator resumes the generator, it continues where it left off.
-
The
finally:block is entered andprint task_name, "took", time.time() - t, "seconds."is executed. -
The generator exits, the decorator
__exit__method exits, all is done.