pythonexceptionpython-2.7with-statementcontextmanager

Catching exception in context manager __enter__()


Is it possible to ensure the __exit__() method is called even if there is an exception in __enter__()?

>>> class TstContx(object):
...    def __enter__(self):
...        raise Exception('Oops in __enter__')
...
...    def __exit__(self, e_typ, e_val, trcbak):
...        print "This isn't running"
... 
>>> with TstContx():
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __enter__
Exception: Oops in __enter__
>>> 

Edit

This is as close as I could get...

class TstContx(object):
    def __enter__(self):
        try:
            # __enter__ code
        except Exception as e
            self.init_exc = e

        return self

    def __exit__(self, e_typ, e_val, trcbak):
        if all((e_typ, e_val, trcbak)):
            raise e_typ, e_val, trcbak

        # __exit__ code


with TstContx() as tc:
    if hasattr(tc, 'init_exc'): raise tc.init_exc

    # code in context

In hind sight, a context manager might have not been the best design decision


Solution

  • Like this:

    import sys
    
    class Context(object):
        def __enter__(self):
            try:
                raise Exception("Oops in __enter__")
            except:
                # Swallow exception if __exit__ returns a True value
                if self.__exit__(*sys.exc_info()):
                    pass
                else:
                    raise
    
    
        def __exit__(self, e_typ, e_val, trcbak):
            print "Now it's running"
    
    
    with Context():
        pass
    

    To let the program continue on its merry way without executing the context block you need to inspect the context object inside the context block and only do the important stuff if __enter__ succeeded.

    class Context(object):
        def __init__(self):
            self.enter_ok = True
    
        def __enter__(self):
            try:
                raise Exception("Oops in __enter__")
            except:
                if self.__exit__(*sys.exc_info()):
                    self.enter_ok = False
                else:
                    raise
            return self
    
        def __exit__(self, e_typ, e_val, trcbak):
            print "Now this runs twice"
            return True
    
    
    with Context() as c:
        if c.enter_ok:
            print "Only runs if enter succeeded"
    
    print "Execution continues"
    

    As far as I can determine, you can't skip the with-block entirely. And note that this context now swallows all exceptions in it. If you wish not to swallow exceptions if __enter__ succeeds, check self.enter_ok in __exit__ and return False if it's True.