Clean up properly during resource acquisition
When acquiring resources in the __enter__ method of a context manager, it is important to ensure that the resources are properly cleaned up if an exception occurs.
In the following example, if acquire_resource2() raises an exception in the __enter__ method, the __exit__ method will not be called, and self._resource1 will not be closed. A similar issue occurs if resource2.close() raises an exception, in which case resource1.close() will not be called.
class MyDevice(Device):
def __enter__(self) -> Self:
self._resource1 = acquire_resource1()
self._resource2 = acquire_resource2()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._resource2.close()
self._resource1.close()
To avoid this issue, you can use a contextlib.ExitStack like in the following example.
class MyDevice(Device):
def __enter__(self) -> Self:
self._stack = contextlib.ExitStack()
try:
self._resource1 = acquire_resource1()
self._stack.callback(resource1.close)
self._resource2 = acquire_resource2()
self._stack.callback(resource2.close)
except:
self._stack.close()
raise
def __exit__(self, exc_type, exc_val, exc_tb):
self._stack.close()