[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