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 yield
line, 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 the yield
line caused that exception. Because you have a finally
clause, 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 yield
expression pauses the generator, control goes back to the decorator. This takes whatever was yielded and returns that to the with
statement, in case there is an as target
part. Here None
is yielded (there is only a plain yield
expression).
[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 and print task_name, "took", time.time() - t, "seconds."
is executed.
The generator exits, the decorator __exit__
method exits, all is done.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…