Jan Kaliszewski schrieb am Do, 20. Okt 2011, um 02:19:22 +0200:
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()
Why introduce an error handler at all? If you want to handle exceptions inside the with statement, an explicit try-except block would be both more convenient (you don't need an extra function definition) and more flexible (you can use - say - 'except ValueError:' to just catch some exceptions). To give an example, I'd really prefer
with CleanupManager() as cleanup: try: # do stuff except ValueError: # handle exception
over
def handle_value_error(exc_type, exc_value, exc_tb): if issubclass(exc_type, ValueError): # handle exception return True return False with CleanupManager(handle_value_error) as cleanup: # do stuff
Instead of an error handler, I'd rather accept the first (or more) callbacks as constructor parameters -- there wouldn't be any point in starting the with block if you weren't about to add a callback in the very next statement, so we could as well accept it as a parameter to the constructor.
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
Note that the only exception you could easily catch in this example is the ZeroDivisionError.
try: 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 except ValueError as e: print(e)
won't catch the ValueError.
Cheers, Sven