Re-assign exception from within a python __exit__ block - python

From within an __exit__ block in a custom cursor class I want to catch an exception so I can in turn throw a more specific exception. What is the proper way to do this?
class Cursor:
def __enter__(self):
...
def __exit__(self, ex_type, ex_val, tb):
if ex_type == VagueThirdPartyError:
# get new more specific error based on error code in ex_val and
# return that one in its place.
return False # ?
else:
return False
Raising the specific exception within the __exit__ block seems like a hack, but maybe I'm over thinking it.

The proper procedure is to raise the new exception inside of the __exit__ handler.
You should not raise the exception that was passed in though; to allow for context manager chaining, in that case you should just return a falsey value from the handler. Raising your own exceptions is however perfectly fine.
Note that it is better to use the identity test is to verify the type of the passed-in exception:
def __exit__(self, ex_type, ex_val, tb):
if ex_type is VagueThirdPartyError:
if ex_val.args[0] == 'foobar':
raise SpecificException('Foobarred!')
# Not raising a new exception, but surpressing the current one:
if ex_val.args[0] == 'eggs-and-ham':
# ignore this exception
return True
if ex_val.args[0] == 'baz':
# re-raise this exception
return False
# No else required, the function exits and `None` is returned
You could also use issubclass(ex_type, VagueThirdPartyError) to allow for subclasses of the specific exception.

Related

Which is worse - duplicated code or double try/except?

I have a situation where I want to do multiple things while handling an exception. Since I want to make this about the general case, I'll translate my specific case into some more general language.
When I have an exception in this piece of code, I want to:
Always perform a rollback-style operation
If it is an
application specific exception, I want to perform some logging and swallow the exception.
So I can think of two ways to solve it, both ugly:
# Method nested-try/except block
try:
try:
do_things()
except:
rollback()
raise
except SpecificException as err:
do_advanced_logging(err)
return
# Method Duplicate Code
try:
do_things()
except SpecificException as err:
rollback()
do_advanced_logging(err)
return
except:
rollback()
raise
Both will have the same behaviour.
I'm tending towards the nested try/except solution myself. While it might be slightly slower, I don't think the speed difference is relevant here - at the very least not for my specific case. Duplication of code is something I want to avoid also because my rollback() statement is slightly more involved that just a database rollback, even if it has the exact same purpose (it involves a web-API).
Is there a third option I haven't spotted that is better? Or is the duplicate code method better? Please note that the rollback() functionality is already factored out as much as possible, but still contains a function call and three arguments which includes a single hardcoded string. Since this string is unique, there's no reason to make it a named constant.
How about checking the exception instance type in code?
# Method .. No Duplicate Code
try:
do_things()
except Exception as e:
rollback()
if isinstance(e, SpecificException):
do_advanced_logging(e)
return
raise
how about putting the rollback in a finally clause? something like:
do_rollback = True
try:
do_things()
do_rollback = False
except SpecificException as err:
do_advanced_logging(err)
finally:
if do_rollback:
rollback()
an alternative is to use an else clause, which would let you do more in the non-exceptional case and not have exceptions all caught in the same place:
do_rollback = True
try:
do_things()
except SpecificException as err:
do_advanced_logging(err)
else:
record_success()
do_rollback = False
finally:
if do_rollback:
rollback()
is useful when record_success can raise a SpecificException, but you don't want to do_advanced_logging
You could write a context manager:
import random
class SpecificException(Exception):
pass
def do_things(wot=None):
print("in do_things, wot = {}".format(wot))
if wot:
raise wot("test")
def rollback():
print("rollback")
def do_advance_logging(exc_type, exc_val, traceback):
print("logging got {} ('{}')".format(exc_type, exc_val))
class rollback_on_error(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, traceback):
# always rollback
rollback()
# log and swallow specific exceptions
if exc_type and issubclass(exc_type, SpecificException):
do_advance_logging(exc_type, exc_val, traceback)
return True
# propagate other exceptions
return False
def test():
try:
with rollback_on_error():
do_things(ValueError)
except Exception as e:
print("expected ValueError, got '{}'".format(type(e)))
else:
print("oops, should have caught a ValueError")
try:
with rollback_on_error():
do_things(SpecificException)
except Exception as e:
print("oops, didn't expect exception '{}' here".format(e))
else:
print("ok, no exception")
try:
with rollback_on_error():
do_things(None)
except Exception as e:
print("oops, didn't expect exception '{}' here".format(e))
else:
print("ok, no exception")
if __name__ == "__main__":
test()
But unless you have dozen occurrences of this pattern, I'd rather stick to the very obvious and perfectly pythonic solutions - either nested exceptions handlers or explicit typecheck (isinstance) in the except clause.

Always perform finally block except for one exception

I have a try:finally block that must execute always (exception or not) unless a specific exception occurs. For the sake of argument let's say it's a ValueError, so I'm asking if I can implement:
try:
stuff()
except Exception as e:
if type(e) is ValueError: raise
#do important stuff
raise
#do important stuff
in a more elegant fashion to skip copy-pasting #importantstuff. If I ruled Python it would look something like:
try:
stuff()
finally except ValueError:
#do important stuff
Putting #importantstuff in a function is not an answer, but not possible is.
If you need finally to skip things in specific conditions, you'll need to use an explicit flag:
do_final_stuff = True
try:
# ...
except ValueError:
do_final_stuff = False
raise
finally:
if do_final_stuff:
# ...
You could also use a context manager here, to clean up afterwards. A context manager is passed the current active exception if there is one:
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
if exc_type is not ValueError:
# do cleanup
with MyContextManager():
# ...

Can we return after raise statement

I need to return True Value after raise statement. Here I need to raise statement as well as it should return True value. If I use finally statement, it will not raise exception block and if I do not use finally then exception block will execute with raise statement and after that I will not able to use retcodecmd variable. Below My Code in python:
try:
something....
except ValueError:
self._retcodecmd = True
raise SomeException(something)
finally:
if self._retcodecmd is True:
return True
else:
return False
Returning and bubbling exceptions out of a function are mutually exclusive. It's nonsensical to exit a function by both raise and return, you have to choose.
The finally block here will force a return, undoing the exception you raised. If that's not what you want, you need to let the exception propagate without being overridden in the finally block and understand how to handle the exception appropriately in a caller.

Catching an exceptions in __enter__ in the calling code in Python

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.
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....
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`.

overwrite file reference variable in with statement

I was curious if this causes any bad behaviors. I ran a test case and got no errors so I assume its OK (although probably not good practice). Just wanted to know how python deals with the issue I assumed should have existed?
with open("somefile.txt","r") as fileinfo:
fileinfo = fileinfo.readlines()
print fileinfo
I thought overwritting "fileinfo" would cause issues exiting the with statement (raise some error about not being able to .close() a list). Does the with statement retain a local copy of the file reference? Thanks!
Of course Python retains an internal reference to the object used in the with statement. Otherwise how would it work when you don't use the as clause?
A with statement does indeed store a local reference to the file object (although I am not positive exactly what is stored in self.gen)
Looked into the topic, specifically researching the context manager and found this which gives slightly more detail for those interested.
class GeneratorContextManager(object):
def __init__(self, gen):
# Store local copy of "file reference"
self.gen = gen
def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")
def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration:
return True
except:
# only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But
# throw() has to raise the exception to signal
# propagation, so this fixes the impedance mismatch
# between the throw() protocol and the __exit__()
# protocol.
#
if sys.exc_info()[1] is not value:
raise
def contextmanager(func):
def helper(*args, **kwds):
return GeneratorContextManager(func(*args, **kwds))
return helper

Categories

Resources