Skip to content Skip to sidebar Skip to footer

How To Safely Handle An Exception Inside A Context Manager

I think I've read that exceptions inside a with do not allow __exit__ to be call correctly. If I am wrong on this note, pardon my ignorance. So I have some pseudo code here, my goa

Solution 1:

The __exit__ method is called as normal if the context manager is broken by an exception. In fact, the parameters passed to __exit__ all have to do with handling this case! From the docs:

object.__exit__(self, exc_type, exc_value, traceback)

Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.

If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.

Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.

So you can see that the __exit__ method will be executed and then, by default, any exception will be re-raised after exiting the context manager. You can test this yourself by creating a simple context manager and breaking it with an exception:

DummyContextManager(object):
    def__enter__(self):
        print('Entering...')
    def__exit__(self, exc_type, exc_value, traceback):
        print('Exiting...')  
        # If we returned True here, any exception would be suppressed!with DummyContextManager() as foo:
    raise Exception()

When you run this code, you should see everything you want (might be out of order since print tends to end up in the middle of tracebacks):

Entering...
Exiting...
Traceback (most recent calllast):
  File "C:\foo.py", line 8, in<module>
    raise Exception()
Exception

Solution 2:

The best practice when using @contextlib.contextmanager was not quite clear to me from the above answer. I followed the link in the comment from @BenUsman.

If you are writing a context manager you must wrap the yield in try-finally block:

from contextlib import contextmanager

@contextmanagerdefmanaged_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

>>> with managed_resource(timeout=3600) as resource:
... # Resource is released at the end of this block,... # even if code in the block raises an exception

Post a Comment for "How To Safely Handle An Exception Inside A Context Manager"