3

Is there a way I can catch exceptions in the __enter__ method of a context manager without wrapping the whole with block inside a try?

class TstContx(object):
    def __enter__(self):
        raise Exception("I'd like to catch this exception")
    def __exit__(self, e_typ, e_val, trcbak):
        pass


with TstContx():
    raise Exception("I don't want to catch this exception")
    pass

I know that I can catch the exception within __enter__() itself, but can I access that error from the function that contains the with statement?

On the surface the question Catching exception in context manager __enter__() seems to be the same thing but that question is actually about making sure that __exit__ gets called, not with treating the __enter__ code differently from the block that the with statement encloses.

...evidently the motivation should be clearer. The with statement is setting up some logging for a fully automated process. If the program fails before the logging is set up, then I can't rely on the logging to notify me, so I have to do something special. And I'd rather achieve the effect without having to add more indentation, like this:

try:
    with TstContx():
        try:
            print "Do something"
        except Exception:
            print "Here's where I would handle exception generated within the body of the with statement"
except Exception:
    print "Here's where I'd handle an exception that occurs in __enter__ (and I suppose also __exit__)"

Another downside to using two try blocks is that the code that handles the exception in __enter__ comes after the code that handles exception in the subsequent body of the with block.

Community
  • 1
  • 1
kuzzooroo
  • 6,788
  • 11
  • 46
  • 84

2 Answers2

3

You can catch the exception using try/except inside of __enter__, then save the exception instance as an instance variable of the TstContx class, allowing you to access it inside of the with block:

class TstContx(object):
    def __enter__(self):
        self.exc = None
        try:
            raise Exception("I'd like to catch this exception")
        except Exception as e:
            self.exc = e 
        return self

    def __exit__(self, e_typ, e_val, trcbak):
        pass    

with TstContx() as tst:
    if tst.exc:
        print("We caught an exception: '%s'" % tst.exc)
    raise Exception("I don't want to catch this exception")

Output:

We caught an exception: 'I'd like to catch this exception'.
Traceback (most recent call last):
  File "./torn.py", line 20, in <module>
    raise Exception("I don't want to catch this exception")
Exception: I don't want to catch this exception

Not sure why you'd want to do this, though....

dano
  • 91,354
  • 19
  • 222
  • 219
  • A related option would be to `return self, exception` or (in a `@contextmanager`) use `yield whatever, exception` where `exception` is `None` if no exception was raised. – kuzzooroo Jul 27 '14 at 20:35
0

You can use contextlib.ExitStack as outlined in this doc example in order to check for __enter__ errors separately:

from contextlib import ExitStack

stack = ExitStack()
try:
    stack.enter_context(TstContx())
except Exception:  # `__enter__` produced an exception.
    pass
else:
    with stack:
        ...  # Here goes the body of the `with`.
a_guest
  • 34,165
  • 12
  • 64
  • 118