0xC0000022L
0xC0000022L

Reputation: 21269

How to manipulate the exception in __exit__ of a context manager?

I know it's bad style to re-raise an exception from within a context manager's __exit__() method. So, I'd like to tack an attribute on the instance which can carry contextual information that isn't available if I let the exception trickle through or if I catch it. This will avoid re-raising it.

The alternative to tacking the attribute on the exception would be to swallow the exception, set some state on the instance that doubles as the context manager in question and later check that state. Problem is that this would lead to a catch 22, wouldn't it? Since the exception means that execution inside the with block is being exited. There is no way to repeat the operation other than entering the with block again, right? So the instance in which I am trying to store the contextual information would go away once the __exit__() method returns.

So in short: how can I manipulate the actual exception that is pending (if it is, which I'll assume as given for this question) while in the __exit__() method?

Upvotes: 11

Views: 4321

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121594

The context manager doesn't go away just because the block exits. You can preserve it in two ways:

  1. Create the context manager first, assign it to a variable, then use with with that object:

    cm = ContextManager()
    with cm:
        # ....
    
    state = cm.attribute
    
  2. Return the context manager itself from the __enter__ method, use with ... as ... to bind that to a local name. That name is not unbound when with exits:

    with ContextManager() as cm:
        # ....
    
    state = cm.attribute
    

    where ContextManager.__enter__ uses return self.

You can also set extra attributes on the exception itself; no need to re-raise the exception:

>>> class ContextManager(object):
...     def __enter__(self):
...         return self
...     def __exit__(self, tp, v, tb):
...         if tp is None: return
...         v.extra_attribute = 'foobar'
...         self.other_extra_attribute = 'spam-n-ham'
... 
>>> try:
...     with ContextManager() as cm:
...         raise ValueError('barfoo')
... except ValueError as ex:
...     print vars(ex)
... 
{'extra_attribute': 'foobar'}
>>> vars(cm)
{'other_extra_attribute': 'spam-n-ham'}

Here the exception was given an extra attribute that persisted all the way to the exception handler. In the above I also show that cm is still bound to the context manager.

Upvotes: 11

Related Questions