Your first example is not a good idea:
-
What happens if
slave_connection.__enter__throws an exception:master_connectionacquires its resourceslave_connectionfailsDataSync.__enter__propogates the exceptionDataSync.__exit__does not runmaster_connectionis never cleaned up!- Potential for Bad Things
-
What happens if
master_connection.__exit__throws an exception?DataSync.__exit__finished earlyslave_connectionis 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
stackcleans upmaster_connection - Everything is ok!
- The with block is exited, and
-
What happens if
master_connection.__exit__throws an exception?- Doesn’t matter,
slave_connectiongets cleaned up before this is called - Everything is ok!
- Doesn’t matter,
-
Ok, what happens if
slave_connection.__exit__throws an exception?ExitStackmakes 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!