[Python-ideas] Avoiding nested for try..finally: atexit for functions?
Sven Marnach
sven at marnach.net
Thu Oct 20 17:11:15 CEST 2011
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
More information about the Python-ideas
mailing list