Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is a context manager's __exit__ triggered when inside a generator?

I'm making something like a task scheduler using generators as coroutines. In the below code, I need to execute the print cleanup deterministically.

It seems from my interaction that releasing the object to the garbage collector causes the context manager to exit. But, I know better than to rely on the timing of a GC. Is it really the GC calling the __exit__ or is it another mechanism?

How can I strictly force print 'cleanup'?

>>> from contextlib import contextmanager
>>> @contextmanager
... def foo():
...     print 'setup'
...     try:
...         yield
...     finally:
...         print 'cleanup'
... 
>>> def bar():
...     with foo():
...         while True:
...             yield 'bar'
... 
>>> b = bar()
>>> b.next()
setup
'bar'
>>> b = None
cleanup
like image 425
Cuadue Avatar asked Nov 06 '13 23:11

Cuadue


People also ask

What does __ exit __ do in Python?

__exit__() method This is a method of ContextManager class. The __exit__ method takes care of releasing the resources occupied with the current code snippet. This method must be executed no matter what after we are done with the resources.

What does the context manager do when you are?

A context manager usually takes care of setting up some resource, e.g. opening a connection, and automatically handles the clean up when we are done with it. Probably, the most common use case is opening a file. The code above will open the file and will keep it open until we are out of the with statement.

Is __ exit __ always called?

__exit__() automatically gets called and closes the file for you, even if an exception is raised inside the with block.

What should __ exit __ return?

__enter__() is provided which returns self while object. __exit__() is an abstract method which by default returns None .


2 Answers

Yes, the GC is calling the __del__ cleanup hook of the generator, which in turn raises a GeneratorExit in the generator function to exit the generator (by calling generator.close()).

This means the context manager __exit__ hook will be called whenever a generator function is cleared from memory.

You can manually close the generator yourself first, with generator.close():

b.close()
like image 87
Martijn Pieters Avatar answered Sep 28 '22 06:09

Martijn Pieters


You have to cause the generator to exit. If the nature of the generator is to look forever, you can use gen.throw() to raise an exception in the generator.

Actually, I just looked at the specification for generators, and they have a method close() that does exactly this (it raises a GeneratorExit() exception inside the generator. So just call gen.close() when you are done with it any any context managers will invoke their exit methods. The generator will eat the exception so you don't need to wrap the close() call in a try block:

>>> b= bar()
>>> b.next()
setup
'bar'
>>> b.close()
cleanup
>>>
like image 32
Evan Avatar answered Sep 28 '22 08:09

Evan