[Python-ideas] Avoiding nested for try..finally: atexit for functions?

Nikolaus Rath Nikolaus at rath.org
Thu Oct 20 04:19:10 CEST 2011


Jan Kaliszewski <zuo-WMSfXiZwWcGxgjU+5Knr6g at public.gmane.org> writes:
> 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()
>
[...]
>
> 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):

Wow, that's really neat! I was in Python 2.x mode and would have tried
to iterate over the callbacks, not knowing what to do with any
exceptions from them.

That said, do you have a suggestion for Python 2.7 as well? Maybe
something like a cleanup error handler?

    class CleanupManager:

        _default_error_handler = lambda exc_type, exc_value, tb: False
        _default_cleanup_error_handler = lambda exc_type, exc_value, tb: True

        def __init__(self, error_handler=_default_error_handler,
                     cleanup_error_handler=_default_cleanup_error_handler):
            self.error_handler = error_handler
            self.cleanup_error_handler = cleanup_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:
                # Saves the exception that we are going to raise at the
                # end (if any)
                exc_info = None
                for cb in self.cleanup_callbacks:
                    try:
                        cb()
                    except:
                        # If returns true, ignore exceptions during cleanup
                        if self.cleanup_error_handler(*sys.exc_info()):
                            pass
                        else:
                            # Only first exception gets propagated
                            if not exc_info:
                                exc_info = sys.exc_info()
                if exc_info:
                    raise exc_info[0], exc_info[1], exc_info[2]

Best,

   -Nikolaus

-- 
 »Time flies like an arrow, fruit flies like a Banana.«

  PGP fingerprint: 5B93 61F8 4EA2 E279 ABF6  02CF A9AD B7F8 AE4E 425C



More information about the Python-ideas mailing list