nesting context managers

Ian Kelly ian.g.kelly at gmail.com
Tue Dec 20 13:55:59 EST 2011


On Tue, Dec 20, 2011 at 11:46 AM, Ian Kelly <ian.g.kelly at gmail.com> wrote:
> On Tue, Dec 20, 2011 at 9:56 AM, Ulrich Eckhardt
> <ulrich.eckhardt at dominolaser.com> wrote:
>> Am 20.12.2011 15:15, schrieb Ulrich Eckhardt:
>>
>>> Let us assume I had a class HTTPClient that has a socket for HTTP and a
>>> logfile for filing logs. I want to make this HTTPClient a context
>>> manager, so that I can write
>>>
>>> with HTTPClient(url) as client:
>>>    pass
>>
>>
>> Actually, I overestimated the task:
>>
>>  class HTTPClient(url):
>>      def __enter__(self):
>>          return self
>>      def __exit__(self, ctx_type, ctx_val, ctx_tb):
>>          self.close()
>>      def close(self):
>>          self._myfile.close()
>>          self._mysocket.close()
>>
>> I'll simply ignore the fact that closing a file can fail if you can't flush
>> buffers. Now, my error was that I have to somehow invoke the file's and
>> socket's enter function in my client's enter function. The default for all
>> files and sockets is that they are already open, they are not opened in the
>> enter function! That way, the client remains usable outside a with context
>> but gives me the desired guarantees inside one.
>>
>> For the case of on-demand allocated stuff, I could write the enter function
>> like this:
>>
>>  def __enter__(self):
>>      # allocate resources
>>      f = ... # open file
>>      s = ... # connect socket
>>      # attach after completion
>>      self._myfile = f
>>      self._mysocket = s
>>
>> To be extra safe or in more complicated scenarios, I could wrap this in a
>> try-except and explicitly close those that were already created, but
>> normally I'd expect the garbage collector to do that for me ... or am I then
>> implicitly assuming a specific implementation?
>
> For full generality, copy the code for the contextlib.nested function
> in Python 2.7 or 3.1 and use that.  Don't use contextlib.nested
> itself, though, because it's deprecated and does not exist in Python
> 3.2.

Also note that in versions that do support the nested with syntax, you
can do something like:

class HTTPClient(url):
    @contextlib.contextmanager
    def _contextmanager(file, socket):
        with file, socket:
            yield
    def __enter__(self):
        self._cm = self._contextmanager(self._myfile, self._mysocket)
        return self._cm.__enter__()
    def __exit__(self, exc_type, exc_value, traceback):
        try:
            return self._cm.__exit__(exc_type, exc_value, traceback)
        finally:
            del self._cm

Cheers,
Ian



More information about the Python-list mailing list