Jan Kaliszewski zuo-WMSfXiZwWcGxgjU+5Knr6g@public.gmane.org writes:
Nikolaus Rath dixit (2011-10-19, 10:43):
with CleanupManager() as mngr: allocate_res1() mngr.register(cleanup_res1) # do stuff allocate_res2() mngr.register(cleanup_res2) # do stuff allocate_res3() mngr.register(cleanup_res3) # do stuff
The mngr object would just run all the registered functions when the block is exited.
[snip]
What would be the best way to handle errors during cleanup? Personally I would log them with logging.exception and discard them, but using the logging module is probably not a good option for contextlib, or is it?
I'd suggest something like the following:
class CleanupManager: _default_error_handler = lambda exc_type, exc_value, tb: False def __init__(self, error_handler=_default_error_handler): self.error_handler = error_handler self.cleanup_callbacks = [] def register(self, callback): self.cleanup_callbacks.append(callback) def __enter__(self): return self def __exit__(self, exc_type, exc_value, tb): try: if exc_value is not None: # if returns True, exception will be suppressed... return self.error_handler(exc_type, exc_value, tb) finally: # ...except something wrong happen when using callbacks self._next_callback() def _next_callback(self): if self.cleanup_callbacks: callback = self.cleanup_callbacks.pop() try: callback() finally: # all callbacks to be used + all errors to be reported self._next_callback()
[...]
Please also note that all cleanup callbacks will be used and, at the same time, no exception will remain unnoticed -- that within the with-block (handled with the error handler), but also that from the error handler as well as those from all cleanup handlers (as long as we talk about Py3.x, with its cool exception chaining feature):
Wow, that's really neat! I was in Python 2.x mode and would have tried to iterate over the callbacks, not knowing what to do with any exceptions from them.
That said, do you have a suggestion for Python 2.7 as well? Maybe something like a cleanup error handler?
class CleanupManager:
_default_error_handler = lambda exc_type, exc_value, tb: False _default_cleanup_error_handler = lambda exc_type, exc_value, tb: True
def __init__(self, error_handler=_default_error_handler, cleanup_error_handler=_default_cleanup_error_handler): self.error_handler = error_handler self.cleanup_error_handler = cleanup_error_handler self.cleanup_callbacks = []
def register(self, callback): self.cleanup_callbacks.append(callback)
def __enter__(self): return self
def __exit__(self, exc_type, exc_value, tb): try: if exc_value is not None: # if returns True, exception will be suppressed... return self.error_handler(exc_type, exc_value, tb) finally: # Saves the exception that we are going to raise at the # end (if any) exc_info = None for cb in self.cleanup_callbacks: try: cb() except: # If returns true, ignore exceptions during cleanup if self.cleanup_error_handler(*sys.exc_info()): pass else: # Only first exception gets propagated if not exc_info: exc_info = sys.exc_info() if exc_info: raise exc_info[0], exc_info[1], exc_info[2]
Best,
-Nikolaus