[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