I'm currently trying to write code to catch exceptions, and depending upon which exception is thrown, a different module will be imported than when no exception was thrown.
try:
import sge_execution_engine as execution_engine
except ImportError:
print "Use local execution engine because SGE support is missing!"
print sys.exc_info() # Print out the exception message
import local_execution_engine as execution_engine
except RuntimeError:
print "Using local execution engine because SGE support is missing!"
print sys.exc_info()
import local_execution_engine as execution_engine
The first exception, ImportError
that is caught, catches the exception thrown when python drmaa
module cannot be found during the execution of import sge_execution_engine
(inside sge_execution_engine
, there is an import drmaa
statement). The second exception, RuntimeError
, is caught when the drmaa
python library is found (likewsie during the execution of the import drmaa
statement inside the sge_execution_engine
), but the drmaa
C library is not installed into the OS. We hope that these two except
statements are sufficient to catch all possible exceptions that can be thrown when a user attempts to run this module on a machine that just does not have the python drmaa
library, the drmaa
C library, or does not have Sun Grid Engine installed. without any of these proceeds, the module proceeds to then import local_execution_engine
and so the code can then execute on the user's machine locally. Right now the code works as expected in the sense that it goes to import local when it finds exceptions with sge, but we are still looking to improve the exception handling here to make it more robust.
In my opinion I think having the actual Exception message that was thrown be printed to stdout is good as it will allow the user to know why he was unable to import sge_execution_engine especially if he was not expecting it to fail being imported.
However, instead of using print sys.exc_info()
to actually have the actual exception message be printed on screen, I realized that perhaps a better way would be to use the except EXCEPTION as some_variable_name
format and then print out print some_variable_name
and also call some of the attributes associated with the Exception that is thrown and assigned to some_variable_name
.
I saw this being done in the Python tutorial on exceptions where there was this chunk of code:
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except IOError as e:
print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
print "Could not convert data to an integer."
except:
print "Unexpected error:", sys.exc_info()[0]
raise
It seems like the except IOError as e
chunk is handling the exception message in a fine-grained way by specifically calling on the errno
and strerror
attributes of the IOError
object. However, when I look at the IOError
documentation , I do not see these specific attributes being listed out as part of the documentation for the exception. In fact, this is also the case for all the other exceptions under the Python documentation, so it seems there is no way we can figure out what attributes will be associated with a particular exception. If we don't know anything about this, then how will we be able to figure out what attributes to call on the some_variable_name
object when we use the import EXCEPTION as some_variable_name
syntax to handle our exceptions?
I would appreciate anyone's suggestion on this, and even if your answer is not directly answering my question, but if you have another entirely different suggestion on how I could better handle my exception here, please don't hesitate to make a post!
Thank you very much!
First you're right that it's better to catch the exception into a variable than to ignore it and then pull it back out with
sys.exc_info()
. There are a few good reasons to useexc_info
(low-level code, code that has to work with both pre-2.6 and 3.x, etc.), but in general, when you get can do it your way, you should.And this works even for your last bare except. In 2.7, a plain
except:
means the same thing asexcept Exception:
, so you can writeexcept Exception as e:
, and then use thee
value.Also note that if you want to do exactly the same thing for multiple exception types, you can write
except (RuntimeError, ImportError) as e:
.As for "making it more robust", that's not an absolute thing. For example, if there's some unexpected exception that's neither a
RuntimeError
nor anImportError
, do you want to log it and try the fallback code, or quite and dump the traceback? If it's, e.g., aSyntaxError
caused by someone checking in a bad edit to your program, you may not want to treat that like a runtime error… or maybe you do; that really depends on your development practices and target userbase.Meanwhile:
You need to look up the hierarchy. Notice that
IOError
is a subclass ofEnvironmentError
, which does document theerrno
andstrerror
attributes. (What these attributes actually mean is only documneted forOSError
and its subclasses, but the fact that they exist is documented.)If you think this is all a bit of a mess… well, it is. It's all cleaned up in Python 3.x, where
IOError
andEnvironmentError
are merged intoOSError
, which clearly documents its attributes, and where you usually don't have to switch onerrno
in the first place because commonerrno
values generate a specific subclass likeFileNotFoundError
, and so on. But as long as you're using 2.7, you don't get the benefits of the last 6 years of improvements to the language.If you
dir
aValueError
, you'll see that it only has two attributes (besides the usual special stuff like__repr__
and__class_
):args
andmessage
.message
isn't documented because it was deprecated in 2.5, and only exists in 2.7 to allow some pre-2.5 code to keep running.* Butargs
is documented; you just need to go up one level further, toBaseException
:So, the reason you can't find the other attributes in
ValueError
is that there are no other attributes to find. And the same goes for those other classes. The handful of types that have special attributes (OSError
,SyntaxError
, maybe a few module-specific types elsewhere in the stdlib) document them explicitly.**It's sufficient to get some useful form of the exception printed. Again, from the
BaseException
docs:In some cases, that's not what you want. In particular, notice that it doesn't include the type of the exception. You can get that with
repr
, which gives you something that looks like a constructor call (e.g.,ValueError("invalid literal for int() with base 10: 'f'")
).If you want the same output you get in a traceback, you have to put the
type
and thestr
orunicode
together yourself—e.g.,'{}: {}'.format(type(e), e)
.If you want to get the actual information out of the exception, like that base
10
or that string'f'
—well, you can't,*** because that information has already been thrown away. You'll have to write your own code to keep track of it, like:* It's as if
BaseException.__init__(self, *args)
were defined to doself.args = tuple(args); self.message = str(args[0]) if args else ''
.** I believe in 2.5 there were a few exception types that had undocumented attributes as an implementation detail of CPython, but by 2.7 they're all either gone or documented.
*** Well, you could parse the exception string, but needless to say, that's an implementation detail, not something guaranteed to be stable and portable. It could be different in Jython, or on a Spanish-language system, or it may not quote strings the way you expect, etc.