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()
Then a user can specify any error callback they need, e.g.:
>>> def error_printer(exc_type, exc_value, tb): ... print(exc_type.__name__, exc_value) ... return True ... >>> with CleanupManager(error_printer) as cm: ... cm.register(lambda: print(1)) ... cm.register(lambda: print(2)) ... raise ValueError('spam') ... ValueError spam 2 1
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):
>>> erroneous_error_handler = (lambda exc_tp, exc, tb: ''/3) >>> with CleanupManager(erroneous_error_handler) as cm: ... cm.register(lambda: 1/0) # error (division by 0) ... cm.register(lambda: 44+'') # error (bad operand type) ... raise ValueError('spam') # error ... Traceback (most recent call last): File "<stdin>", line 4, in <module> ValueError: spam
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "cleanup_manager.py", line 19, in __exit__ return self.error_handler(exc_type, exc_value, tb) File "<stdin>", line 1, in <lambda> TypeError: unsupported operand type(s) for /: 'str' and 'int'
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "cleanup_manager.py", line 28, in _next_callback callback() File "<stdin>", line 3, in <lambda> TypeError: unsupported operand type(s) for +: 'int' and 'str'
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "<stdin>", line 4, in <module> File "cleanup_manager.py", line 22, in __exit__ self._next_callback() File "cleanup_manager.py", line 31, in _next_callback self._next_callback() File "cleanup_manager.py", line 28, in _next_callback callback() File "<stdin>", line 2, in <lambda> ZeroDivisionError: division by zero
Cheers. *j