What gives without value in the context manager

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 is doproc running when using this decorator?

+10
source share
3 answers

yield expression returns control over what is used by the generator. The generator stops at this point, which means that the @contextmanager decorator knows that the code is being executed using the installation part.

In other words, everything you want to do in the __enter__ context manager phase must take place before yield .

As soon as your context exits (so that the block in the with statement is executed), the @contextmanager decorator @contextmanager called for the __exit__ part of the context manager protocol and performs one of two tasks:

  • If there was no exception, he will resume the generator. Thus, your generator is turned off in the yield line, and you enter the cleanup phase, part

  • If an exception occurs, the decorator uses generator.throw() to raise that exception in the generator. It will be like the yield line raised this exception. Since you have a finally clause, it will be executed before your generator exits due to an exception.

So, in your specific example, the sequence looks like this:

  • with time_print("processes"):

    This creates the context manager and calls __enter__ on it.

  • The generator starts execution, t = time.time() is executed.

  • The yield expression pauses the generator; control returns to the decorator. This takes what was received and returns it to the with statement if there is an as target part. None is given here (there is only a simple yield expression).

  • [doproc() for _ in range(500)] is executed and completed.

  • The __exit__ context manager is launched, no exception is thrown.

  • The decorator resumes the generator; it continues where it left off.

  • The finally: block is print task_name, "took", time.time() - t, "seconds." finally: and print task_name, "took", time.time() - t, "seconds." .

  • The generator exits, completes the decorator __exit__ method.

+13
source

Here is a simple demo that can help explain this:

 #!/usr/bin/env python3 def yield_nothing(): # Use a range rather than "while True" so it exits # after a limited number of results. "i" isn't used # anywhere. for i in range(1,100): yield for value in yield_nothing(): print(f"this call to yield_nothing yielded {value}") 
0
source

Great explanation from @Martijn Pieters. Since yield is redundant in your case, you can achieve the same by creating your own context manager (without yield and contextlib.contextmanager ). It is simpler and more understandable. 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 magic functions __enter__ and __exit__, as explained by @Martijun Pieters. Hope this helps!

0
source

Source: https://habr.com/ru/post/1243337/


All Articles