Each Answer to this Q is separated by one/two green lines.
import contextlib import time @contextlib.contextmanager def time_print(task_name): t = time.time() try: yield finally: print task_name, "took", time.time() - t, "seconds." def doproc(): x=1+1 with time_print("processes"): [doproc() for _ in range(500)] # processes took 15.236166954 seconds.
when does doproc get executed when using this decorator?
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
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 the
yieldline caused that exception. Because you have a
finallyclause, it’ll be executed before your generator exits because of the exception.
So, in your specific example the sequence is as follows:
This creates the context manager and calls
The generator starts execution,
t = time.time()is run.
yieldexpression pauses the generator, control goes back to the decorator. This takes whatever was yielded and returns that to the
withstatement, in case there is an
as targetpart. Here
Noneis yielded (there is only a plain
[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.
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.
Excellent explanation by @Martijn Pieters. Since the yield is redundant in your case, you can achieve the same by creating your own context manager (without yield and contextlib.contextmanager). This is simpler and more readable. So in your case you can implement something as follows.
import time class time_print(object): def __init__(self, task_name): self.task_name = task_name def __enter__(self): self.t = time.time() def __exit__(self): print self.task_name, "took", time.time() - self.t, "seconds." def doproc(): x = 1 + 1 with time_print("processes"): # __enter__ is called [doproc() for _ in range(500)] # __exit__ is called
Internally contextlib.contextmanager calls these enter and exit magic functions as explained by @Martijn-Pieters. Hope this helps!