Your first example is not a good idea:
-
What happens if
slave_connection.__enter__
throws an exception:master_connection
acquires its resourceslave_connection
failsDataSync.__enter__
propogates the exceptionDataSync.__exit__
does not runmaster_connection
is never cleaned up!- Potential for Bad Things
-
What happens if
master_connection.__exit__
throws an exception?DataSync.__exit__
finished earlyslave_connection
is never cleaned up!- Potential for Bad Things
contextlib.ExitStack
can help here:
def __enter__(self):
with ExitStack() as stack:
stack.enter_context(self.master_connection)
stack.enter_context(self.slave_connection)
self._stack = stack.pop_all()
return self
def __exit__(self, exc_type, exc, traceback):
self._stack.__exit__(self, exc_type, exc, traceback)
Asking the same questions:
-
What happens if
slave_connection.__enter__
throws an exception:- The with block is exited, and
stack
cleans upmaster_connection
- Everything is ok!
- The with block is exited, and
-
What happens if
master_connection.__exit__
throws an exception?- Doesn’t matter,
slave_connection
gets cleaned up before this is called - Everything is ok!
- Doesn’t matter,
-
Ok, what happens if
slave_connection.__exit__
throws an exception?ExitStack
makes sure to callmaster_connection.__exit__
whatever happens to the slave connection- Everything is ok!
There’s nothing wrong with calling __enter__
directly, but if you need to call it on more than one object, make sure you clean up properly!