[Python-ideas] Avoiding nested for try..finally: atexit for functions?
Jan Kaliszewski
zuo at chopin.edu.pl
Thu Oct 20 02:19:22 CEST 2011
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
More information about the Python-ideas
mailing list